diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e8290fb36b28e8638d5ce493f026f1e4c8853c6a..c23a7a3bf0ecd5615d8282a557349c1cdec2b2d1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,10 +8,11 @@ before_script:
   - touch log/application.log
   - touch log/test.log
   - bundle install --without postgres production --jobs $(nproc)  "${FLAGS[@]}"
-  - bundle exec rake db:create RAILS_ENV=test
+  - bundle exec rake db:reset db:create RAILS_ENV=test
 
 spec:feature:
   script:
+    - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
     - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
   tags:
     - ruby
@@ -24,6 +25,27 @@ spec:api:
     - ruby
     - mysql
 
+spec:models:
+  script:
+    - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
+  tags:
+    - ruby
+    - mysql
+
+spec:lib:
+  script:
+    - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
+  tags:
+    - ruby
+    - mysql
+
+spec:services:
+  script:
+    - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
+  tags:
+    - ruby
+    - mysql
+
 spec:benchmark:
   script:
     - RAILS_ENV=test bundle exec rake spec:benchmark
@@ -39,9 +61,16 @@ spec:other:
     - ruby
     - mysql
 
-spinach:project:
+spinach:project:half:
+  script:
+    - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
+  tags:
+    - ruby
+    - mysql
+
+spinach:project:rest:
   script:
-    - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project
+    - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
   tags:
     - ruby
     - mysql
diff --git a/.rubocop.yml b/.rubocop.yml
index 11e4502849a2f909983a4f05b9e8ff6c3f2437ad..89aa0591c3135ce26db65e2ef769c74b2b0812ff 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -76,7 +76,7 @@ Style/BlockEndNewline:
   Description: 'Put end statement of multiline block on its own line.'
   Enabled: true
 
-Style/Blocks:
+Style/BlockDelimiters:
   Description: >-
                 Avoid using {...} for multi-line blocks (multiline chaining is
                 always ugly).
@@ -232,6 +232,10 @@ Style/EvenOdd:
   StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'
   Enabled: false
 
+Style/ExtraSpacing:
+  Description: 'Do not use unnecessary spacing.'
+  Enabled: false
+
 Style/FileName:
   Description: 'Use snake_case for source file names.'
   StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files'
@@ -431,6 +435,14 @@ Style/OpMethod:
   StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg'
   Enabled: false
 
+Style/ParallelAssignment:
+  Description: >-
+                  Check for simple usages of parallel assignment.
+                  It will only warn when the number of variables
+                  matches on both sides of the assignment.
+  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parallel-assignment'
+  Enabled: false
+
 Style/ParenthesesAroundCondition:
   Description: >-
                  Don't use parentheses around the condition of an
@@ -669,6 +681,13 @@ Style/TrailingWhitespace:
   StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace'
   Enabled: false
 
+Style/TrailingUnderscoreVariable:
+  Description: >-
+                 Checks for the usage of unneeded trailing underscores at the
+                 end of parallel variable assignment.
+  AllowNamedUnderscoreVariables: true
+  Enabled: false
+
 Style/TrivialAccessors:
   Description: 'Prefer attr_* methods to trivial readers/writers.'
   StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family'
@@ -690,11 +709,6 @@ Style/UnneededPercentQ:
   StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q'
   Enabled: false
 
-Style/UnneededPercentX:
-  Description: 'Checks for %x when `` would do.'
-  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-x'
-  Enabled: false
-
 Style/VariableInterpolation:
   Description: >-
                  Don't interpolate global, instance and class variables
@@ -735,23 +749,39 @@ Metrics/AbcSize:
   Description: >-
                  A calculated magnitude based on number of assignments,
                  branches, and conditions.
-  Enabled: false
+  Enabled: true
+  Max: 70
+
+Metrics/CyclomaticComplexity:
+  Description: >-
+                 A complexity metric that is strongly correlated to the number
+                 of test cases needed to validate a method.
+  Enabled: true
+  Max: 17
+
+Metrics/PerceivedComplexity:
+  Description: >-
+                 A complexity metric geared towards measuring complexity for a
+                 human reader.
+  Enabled: true
+  Max: 17
+
+Metrics/ParameterLists:
+  Description: 'Avoid parameter lists longer than three or four parameters.'
+  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
+  Enabled: true
+  Max: 8
 
 Metrics/BlockNesting:
   Description: 'Avoid excessive block nesting'
   StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count'
-  Enabled: false
+  Enabled: true
+  Max: 4
 
 Metrics/ClassLength:
   Description: 'Avoid classes longer than 100 lines of code.'
   Enabled: false
 
-Metrics/CyclomaticComplexity:
-  Description: >-
-                 A complexity metric that is strongly correlated to the number
-                 of test cases needed to validate a method.
-  Enabled: false
-
 Metrics/LineLength:
   Description: 'Limit lines to 80 characters.'
   StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
@@ -762,15 +792,8 @@ Metrics/MethodLength:
   StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods'
   Enabled: false
 
-Metrics/ParameterLists:
-  Description: 'Avoid parameter lists longer than three or four parameters.'
-  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
-  Enabled: false
-
-Metrics/PerceivedComplexity:
-  Description: >-
-                 A complexity metric geared towards measuring complexity for a
-                 human reader.
+Metrics/ModuleLength:
+  Description: 'Avoid modules longer than 100 lines of code.'
   Enabled: false
 
 #################### Lint ################################
@@ -888,7 +911,7 @@ Lint/RequireParentheses:
 Lint/RescueException:
   Description: 'Avoid rescuing the Exception class.'
   StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues'
-  Enabled: false
+  Enabled: true
 
 Lint/ShadowingOuterLocalVariable:
   Description: >-
@@ -956,6 +979,12 @@ Rails/ActionFilter:
   Description: 'Enforces consistent use of action filter methods.'
   Enabled: true
 
+Rails/Date:
+  Description: >-
+                  Checks the correct usage of date aware methods,
+                  such as Date.today, Date.current etc.
+  Enabled: false
+
 Rails/DefaultScope:
   Description: 'Checks if the argument passed to default_scope is a block.'
   Enabled: false
@@ -982,6 +1011,12 @@ Rails/ScopeArgs:
   Description: 'Checks the arguments of ActiveRecord scopes.'
   Enabled: false
 
+Rails/TimeZone:
+  Description: 'Checks the correct usage of time zone aware methods.'
+  StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time'
+  Reference: 'http://danilenko.org/2012/7/6/rails_timezones'
+  Enabled: false
+
 Rails/Validation:
   Description: 'Use validates :attribute, hash of validations.'
   Enabled: false
diff --git a/.ruby-version b/.ruby-version
index 399088bf465606fc2257b2741338f95e9c790390..04b10b4f150135c42748aead459c7ae6caa77c16 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.1.6
+2.1.7
diff --git a/CHANGELOG b/CHANGELOG
index 2453b35ead3edad76c03556df1c2c8c47d045c3e..ea918c5ae0186964fa7d6416ab4b637fc2241f0a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,15 +1,123 @@
 Please view this file on the master branch, on stable branches it's out of date.
 
-v 8.3.0 (unreleased)
+v 8.4.0 (unreleased)
+  - Add support for Google reCAPTCHA in user registration to prevent spammers (Stan Hu)
+  - Implement new UI for group page
+  - Implement search inside emoji picker
+  - Add API support for looking up a user by username (Stan Hu)
+  - Add project permissions to all project API endpoints (Stan Hu)
+  - Only allow group/project members to mention `@all`
+  - Expose Git's version in the admin area
+  - Add "Frequently used" category to emoji picker
+  - Add CAS support (tduehr)
+  - Add link to merge request on build detail page.
+  - Revert back upvote and downvote button to the issue and MR pages
+
+v 8.3.2 (unreleased)
+  - Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu)
+  - Enable "Add key" button when user fills in a proper key
+
+v 8.3.1
+  - Fix Error 500 when global milestones have slashes (Stan Hu)
+  - Fix Error 500 when doing a search in dashboard before visiting any project (Stan Hu)
+  - Fix LDAP identity and user retrieval when special characters are used
+  - Move Sidekiq-cron configuration to gitlab.yml
+  - Enable forcing Two-Factor authentication sitewide, with optional grace period
+
+v 8.3.0
+  - Bump rack-attack to 4.3.1 for security fix (Stan Hu)
+  - API support for starred projects for authorized user (Zeger-Jan van de Weg)
+  - Add open_issues_count to project API (Stan Hu)
+  - Expand character set of usernames created by Omniauth (Corey Hinshaw)
+  - Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg)
+  - Provide better diagnostic message upon project creation errors (Stan Hu)
+  - Bump devise to 3.5.3 to fix reset token expiring after account creation (Stan Hu)
+  - Remove api credentials from link to build_page
+  - Deprecate GitLabCiService making it to always be inactive
+  - Bump gollum-lib to 4.1.0 (Stan Hu)
+  - Fix broken group avatar upload under "New group" (Stan Hu)
+  - Update project repositorize size and commit count during import:repos task (Stan Hu)
+  - Fix API setting of 'public' attribute to false will make a project private (Stan Hu)
+  - Handle and report SSL errors in Web hook test (Stan Hu)
+  - Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu)
   - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
   - WIP identifier on merge requests no longer requires trailing space
+  - Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
+  - Fix 500 error when update group member permission
+  - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
+  - Recognize issue/MR/snippet/commit links as references
+  - Backport JIRA features from EE to CE
+  - Add ignore whitespace change option to commit view
+  - Fire update hook from GitLab
+  - Allow account unlock via email
+  - Style warning about mentioning many people in a comment
+  - Fix: sort milestones by due date once again (Greg Smethells)
+  - Migrate all CI::Services and CI::WebHooks to Services and WebHooks
+  - Don't show project fork event as "imported"
+  - Add API endpoint to fetch merge request commits list
+  - Don't create CI status for refs that doesn't have .gitlab-ci.yml, even if the builds are enabled
+  - Expose events API with comment information and author info
+  - Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583
+  - Run custom Git hooks when branch is created or deleted.
+  - Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch
+  - Add languages page to graphs
+  - Block LDAP user when they are no longer found in the LDAP server
+  - Improve wording on project visibility levels (Zeger-Jan van de Weg)
+  - Fix editing notes on a merge request diff
+  - Automatically select default clone protocol based on user preferences (Eirik Lygre)
+  - Make Network page as sub tab of Commits
+  - Add copy-to-clipboard button for Snippets
+  - Add indication to merge request list item that MR cannot be merged automatically
+  - Default target branch to patch-n when editing file in protected branch
+  - Add Builds tab to merge request detail page
+  - Allow milestones, issues and MRs to be created from dashboard and group indexes
+  - Use new style for wiki
+  - Use new style for milestone detail page
+  - Fix sidebar tooltips when collapsed
+  - Prevent possible XSS attack with award-emoji
+  - Upgraded Sidekiq to 4.x
+  - Accept COPYING,COPYING.lesser, and licence as license file (Zeger-Jan van de Weg)
+  - Fix emoji aliases problem
+  - Fix award-emojis Flash alert's width
+  - Fix deleting notes on a merge request diff
+  - Display referenced merge request statuses in the issue description (Greg Smethells)
+  - Implement new sidebar for issue and merge request pages
+  - Emoji picker improvements
+  - Suppress warning about missing `.gitlab-ci.yml` if builds are disabled
+  - Do not show build status unless builds are enabled and `.gitlab-ci.yml` is present
+  - Persist runners registration token in database
+  - Fix online editor should not remove newlines at the end of the file
+
+v 8.2.3
+  - Fix application settings cache not expiring after changes (Stan Hu)
+  - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu)
+  - Update documentation for "Guest" permissions
+  - Properly convert Emoji-only comments into Award Emojis
+  - Enable devise paranoid mode to prevent user enumeration attack
+  - Webhook payload has an added, modified and removed properties for each commit
+  - Fix 500 error when creating a merge request that removes a submodule
+
+v 8.2.2
+  - Fix 404 in redirection after removing a project (Stan Hu)
+  - Ensure cached application settings are refreshed at startup (Stan Hu)
+  - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu)
+  - Fix: Raw private snippets access workflow
+  - Prevent "413 Request entity too large" errors when pushing large files with LFS
+  - Fix: As an admin, cannot add oneself as a member to a group/project
+  - Fix invalid links within projects dashboard header
+  - Make current user the first user in assignee dropdown in issues detail page (Stan Hu)
+  - Fix: duplicate email notifications on issue comments
+
+v 8.2.1
+  - Forcefully update builds that didn't want to update with state machine
+  - Fix: saving GitLabCiService as Admin Template
 
 v 8.2.0
   - Improved performance of finding projects and groups in various places
   - Improved performance of rendering user profile pages and Atom feeds
+  - Expose build artifacts path as config option
   - Fix grouping of contributors by email in graph.
   - Improved performance of finding issues with/without labels
-  - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu)
   - Fix Drone CI service template not saving properly (Stan Hu)
   - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu)
   - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749)
@@ -48,7 +156,6 @@ v 8.2.0
   - Add email notification to former assignee upon unassignment (Adam Lieskovský)
   - New design for project graphs page
   - Remove deprecated dumped yaml file generated from previous job definitions
-  - Fix incoming email config defaults
   - Show specific runners from projects where user is master or owner
   - MR target branch is now visible on a list view when it is different from project's default one
   - Improve Continuous Integration graphs page
@@ -206,7 +313,6 @@ v 8.0.2
   - Allow AWS S3 Server-Side Encryption with Amazon S3-Managed Keys for backups (Paul Beattie)
 
 v 8.0.1
-  - Remove git refs used internally by GitLab from network graph (Stan Hu)
   - Improve CI migration procedure and documentation
 
 v 8.0.0
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 007e410b67774b14319871914792c2f81c5b7303..b9c2b3d2f8e8242009998736c80c1303a90a2c9d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,103 +1,265 @@
 # Contribute to GitLab
 
-Thank you for your interest in contributing to GitLab.
-This guide details how contribute to GitLab in a way that is efficient for everyone.
-If you have read this guide and want to know how the GitLab core-team operates please see [the GitLab contributing process](PROCESS.md).
+Thank you for your interest in contributing to GitLab. This guide details how
+to contribute to GitLab in a way that is efficient for everyone.
+
+GitLab comes into two flavors, GitLab Community Edition (CE) our free and open
+source edition, and GitLab Enterprise Edition (EE) which is our commercial
+edition. Throughout this guide you will see references to CE and EE for
+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).
 
 ## Contributor license agreement
 
-By submitting code as an individual you agree to the [individual contributor license agreement](doc/legal/individual_contributor_license_agreement.md). By submitting code as an entity you agree to the [corporate contributor license agreement](doc/legal/corporate_contributor_license_agreement.md).
+By submitting code as an individual you agree to the
+[individual contributor license agreement](doc/legal/individual_contributor_license_agreement.md).
+By submitting code as an entity you agree to the
+[corporate contributor license agreement](doc/legal/corporate_contributor_license_agreement.md).
 
 ## Security vulnerability disclosure
 
-Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](https://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
+Please report suspected security vulnerabilities in private to
+`support@gitlab.com`, also see the
+[disclosure section on the GitLab.com website](https://about.gitlab.com/disclosure/).
+Please do **NOT** create publicly viewable issues for suspected security
+vulnerabilities.
 
 ## Closing policy for issues and merge requests
 
-GitLab is a popular open source project and the capacity to deal with issues and merge requests is limited. Out of respect for our volunteers, issues and merge requests not in line with the guidelines listed in this document may be closed without notice.
+GitLab is a popular open source project and the capacity to deal with issues
+and merge requests is limited. Out of respect for our volunteers, issues and
+merge requests not in line with the guidelines listed in this document may be
+closed without notice.
 
-Please treat our volunteers with courtesy and respect, it will go a long way towards getting your issue resolved.
+Please treat our volunteers with courtesy and respect, it will go a long way
+towards getting your issue resolved.
 
-Issues and merge requests should be in English and contain appropriate language for audiences of all ages.
+Issues and merge requests should be in English and contain appropriate language
+for audiences of all ages.
 
 ## Helping others
 
-Please help other GitLab users when you can.
-The channels people will reach out on can be found on the [getting help page](https://about.gitlab.com/getting-help/).
-Sign up for the mailinglist, answer GitLab questions on StackOverflow or respond in the IRC channel.
-You can also sign up on [CodeTriage](http://www.codetriage.com/gitlabhq/gitlabhq) to help with one issue every day.
+Please help other GitLab users when you can. The channels people will reach out
+on can be found on the [getting help page][].
+
+Sign up for the mailing list, answer GitLab questions on StackOverflow or
+respond in the IRC channel. You can also sign up on [CodeTriage][] to help with
+the remaining issues on the GitHub issue tracker.
+
+## I want to contribute!
+
+If you want to contribute to GitLab, but are not sure where to start,
+look for [issues with the label `up-for-grabs`][up-for-grabs]. These issues
+will be of reasonable size and challenge, for anyone to start contributing to
+GitLab.
+
+This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs].
 
 ## Issue tracker
 
-To get support for your particular problem please use the [getting help channels](https://about.gitlab.com/getting-help/).
+To get support for your particular problem please use the
+[getting help channels](https://about.gitlab.com/getting-help/).
+
+The [GitLab CE issue tracker on GitLab.com][ce-tracker] is for bugs concerning
+the latest GitLab release and [feature proposals](#feature-proposals).
+
+When submitting an issue please conform to the issue submission guidelines
+listed below. Not all issues will be addressed and your issue is more likely to
+be addressed if you submit a merge request which partially or fully solves
+the issue.
+
+If you're unsure where to post, post to the [mailing list][google-group] or
+[Stack Overflow][stackoverflow] first. There are a lot of helpful GitLab users
+there who may be able to help you quickly. If your particular issue turns out
+to be a bug, it will find its way from there.
+
+If it happens that you know the solution to an existing bug, please first
+open the issue in order to keep track of it and then open the relevant merge
+request that potentially fixes it.
+
+### Feature proposals
+
+To create a feature proposal for CE and CI, open an issue on the
+[issue tracker of CE][ce-tracker].
 
-The [GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
+For feature proposals for EE, open an issue on the
+[issue tracker of EE][ee-tracker].
 
-Do not use the issue tracker for feature requests. We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose. Please keep feature requests as small and simple as possible, complex ones might be edited to make them small and simple.
+In order to help track the feature proposals, we have created a
+[`feature proposal`][fpl] label. For the time being, users that are not members
+of the project cannot add labels. You can instead ask one of the [core team][]
+members to add the label `feature proposal` to the issue.
 
-Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](https://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there.
+Please keep feature proposals as small and simple as possible, complex ones
+might be edited to make them small and simple.
+
+For changes in the interface, it can be helpful to create a mockup first.
+If you want to create something yourself, consider opening an issue first to
+discuss whether it is interesting to include this in GitLab.
 
 ### Issue tracker guidelines
 
-**[Search the issues](https://gitlab.com/gitlab-org/gitlab-ce/issues)** for similar entries before submitting your own, there's a good chance somebody else had the same issue. Show your support with `:+1:` and/or join the discussion. Please submit issues in the following format (as the first post):
+**[Search the issue tracker][ce-tracker]** for similar entries before
+submitting your own, there's a good chance somebody else had the same issue or
+feature proposal. Show your support with an award emoji and/or join the
+discussion.
+
+Please submit bugs using the following template in the issue description area.
+The text in the parenthesis is there to help you with what to include. Omit it
+when submitting the actual issue. You can copy-paste it and then edit as you
+see fit.
+
+```
+## Summary
+
+(Summarize your issue in one sentence - what goes wrong, what did you expect to happen)
+
+## Steps to reproduce
+
+(How one can reproduce the issue - this is very important)
+
+## Expected behavior
+
+(What you should see instead)
+
+## Relevant logs and/or screenshots
+
+(Paste any relevant logs - please use code blocks (```) to format console output,
+logs, and code as it's very hard to read otherwise.)
+
+## Output of checks
 
-1. **Summary:** Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
-1. **Steps to reproduce:** How can we reproduce the issue
-1. **Expected behavior:** Describe your issue in detail
-1. **Observed behavior**
-1. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise.
-1. **Output of checks**
-    * Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing
-    * Version of GitLab you are running; we will only investigate issues in the latest stable and development releases as per the [maintenance policy](MAINTENANCE.md)
-    * Add the last commit SHA-1 of the GitLab version you used to replicate the issue (obtainable from the help page)
-    * Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
-1. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem
+### Results of GitLab Application Check
+
+(For installations with omnibus-gitlab package run and paste the output of:
+sudo gitlab-rake gitlab:check SANITIZE=true)
+
+(For installations from source run and paste the output of:
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true)
+
+(we will only investigate if the tests are passing)
+
+### Results of GitLab Environment Info
+
+(For installations with omnibus-gitlab package run and paste the output of:
+sudo gitlab-rake gitlab:env:info)
+
+(For installations from source run and paste the output of:
+sudo -u git -H bundle exec rake gitlab:env:info)
+
+## Possible fixes
+
+(If you can, link to the line of code that might be responsible for the problem)
+
+```
+
+### Issue weight
+
+Issue weight allows us to get an idea of the amount of work required to solve
+one or multiple issues. This makes it possible to schedule work more accurately.
+
+You are encouraged to set the weight of any issue. Following the guidelines
+below will make it easy to manage this, without unnecessary overhead.
+
+1. Set weight for any issue at the earliest possible convenience
+1. If you don't agree with a set weight, discuss with other developers until
+consensus is reached about the weight
+1. Issue weights are an abstract measurement of complexity of the issue. Do not
+relate issue weight directly to time. This is called [anchoring](https://en.wikipedia.org/wiki/Anchoring)
+and something you want to avoid.
+1. Something that has a weight of 1 (or no weight) is really small and simple.
+Something that is 9 is rewriting a large fundamental part of GitLab,
+which might lead to many hard problems to solve. Changing some text in GitLab
+is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9.
+1. If something is very large, it should probably be split up in multiple
+issues or chunks. You can simply not set the weight of a parent issue and set
+weights to children issues.
 
 ## Merge requests
 
-We welcome merge requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a merge request for are listed with the [status 'accepting merge requests' on our feature request forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome. If you want to add a new feature that is not marked it is best to first create a feedback issue (if there isn't one already) and leave a comment asking for it to be marked accepting merge requests. Please include screenshots or wireframes if the feature will also change the UI.
+We welcome merge requests with fixes and improvements to GitLab code, tests,
+and/or documentation. The features we would really like a merge request for are
+listed with the label [`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
+and [EE][accepting-mrs-ee] but other improvements are also welcome.
+
+If you want to add a new feature that is not labeled it is best to first create
+a feedback issue (if there isn't one already) and leave a comment asking for it
+to be marked as `Accepting merge requests`. Please include screenshots or
+wireframes if the feature will also change the UI.
 
-Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls).
+Merge requests can be filed either at [GitLab.com][gitlab-mr-tracker] or at
+[github.com][github-mr-tracker].
 
-If you are new to GitLab development (or web development in general), search for the label `easyfix` ([GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [GitHub](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint.
+If you are new to GitLab development (or web development in general), see the
+[I want to contribute!](#i-want-to-contribute) section to get you started with
+some potentially easy issues.
 
-To start with GitLab download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit) and see [Development section](doc/development/README.md) in the help file.
+To start with GitLab development download the [GitLab Development Kit][gdk] and
+see the [Development section](doc/development/README.md) for some guidelines.
 
 ### Merge request guidelines
 
-If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows:
+If you can, please submit a merge request with the fix or improvements
+including tests. If you don't know how to fix the issue but can write a test
+that exposes the issue we will accept that as well. In general bug fixes that
+include a regression test are merged quickly while new features without proper
+tests are least likely to receive timely feedback. The workflow to make a merge
+request is as follows:
 
 1. Fork the project into your personal space on GitLab.com
 1. Create a feature branch
 1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
 1. Add your changes to the [CHANGELOG](CHANGELOG)
-1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message
-1. If you have multiple commits please combine them into one commit by [squashing them](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
-1. Push the commit to your fork
+1. If you are changing the README, some documentation or other things which
+   have no effect on the tests, add `[ci skip]` somewhere in the commit message
+1. If you have multiple commits please combine them into one commit by
+   [squashing them][git-squash]
+1. Push the commit(s) to your fork
 1. Submit a merge request (MR) to the master branch
 1. The MR title should describe the change you want to make
-1. The MR description should give a motive for your change and the method you used to achieve it
+1. The MR description should give a motive for your change and the method you
+   used to achieve it
 1. If the MR changes the UI it should include before and after screenshots
-1. If the MR changes CSS classes please include the list of affected pages `grep css-class ./app -R`
-1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feature requests](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR
-1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submission
-1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines](    doc/development/shell_commands.md).
-1. Also have a look at the [shell command guidelines](doc/development/shell_commands.md) if your code reads or opens files, or handles paths to files on disk.
-1. If your code creates new files on disk please read the [shared files guidelines](doc/development/shared_files.md).
-
-The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast.
-Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as regressions requiring patch releases.
-After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features.
-
-Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it.
-
-For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the contribution acceptance criteria.
+1. If the MR changes CSS classes please include the list of affected pages
+   `grep css-class ./app -R`
+1. Link any relevant [issues][ce-tracker] in the merge request description and
+   leave a comment on them with a link back to the MR
+1. Be prepared to answer questions and incorporate feedback even if requests
+   for this arrive weeks or months after your MR submission
+1. If your MR touches code that executes shell commands, reads or opens files or
+   handles paths to files on disk, make sure it adheres to the
+   [shell command guidelines](doc/development/shell_commands.md)
+1. If your code creates new files on disk please read the
+   [shared files guidelines](doc/development/shared_files.md).
+
+The **official merge window** is in the beginning of the month from the 1st to
+the 7th day of the month. This is the best time to submit an MR and get
+feedback fast. Before this time the GitLab Inc. team is still dealing with work
+that is created by the monthly release such as regressions requiring patch
+releases. After the 7th it is already getting closer to the release date of the
+next version. This means there is less time to fix the issues created by
+merging large new features.
+
+Please keep the change in a single MR **as small as possible**. If you want to
+contribute a large feature think very hard what the minimum viable change is.
+Can you split the functionality? Can you only submit the backend/API code? Can
+you start with a very simple UI? Can you do part of the refactor? The increased
+reviewability of small MRs that leads to higher code quality is more important
+to us than having a minimal commit log. The smaller an MR is the more likely it
+is it will be merged (quickly). After that you can send more MRs to enhance it.
+
+For examples of feedback on merge requests please look at already
+[closed merge requests][]. If you would like quick feedback on your merge
+request feel free to mention one of the Merge Marshalls of the [core team][].
+Please ensure that your merge request meets the contribution acceptance criteria.
 
 ## Definition of done
 
-If you contribute to GitLab please know that changes involve more than just code.
-We have the following [definition of done](http://guide.agilealliance.org/guide/definition-of-done.html).
-Please ensure you support the feature you contribute through all of these steps.
+If you contribute to GitLab please know that changes involve more than just
+code. We have the following [definition of done][]. Please ensure you support
+the feature you contribute through all of these steps.
 
 1. Description explaining the relevancy (see following item)
 1. Working and clean code that is commented where needed
@@ -111,14 +273,16 @@ Please ensure you support the feature you contribute through all of these steps.
 1. Community questions answered
 1. Answers to questions radiated (in docs/wiki/etc.)
 
-If you add a dependency in GitLab (such as an operating system package) please consider updating the following and note the applicability of each in your merge request:
+If you add a dependency in GitLab (such as an operating system package) please
+consider updating the following and note the applicability of each in your
+merge request:
 
 1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/
 1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md
 1. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool
 1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies
 1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit
-1. Test suite https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/examples/configure_a_runner_to_run_the_gitlab_ce_test_suite.md
+1. Test suite https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh
 1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab
 
 ## Merge request description format
@@ -126,59 +290,111 @@ If you add a dependency in GitLab (such as an operating system package) please c
 1. What does this MR do?
 1. Are there points in the code the reviewer needs to double check?
 1. Why was this MR needed?
-1. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)?
+1. What are the relevant issue numbers?
 1. Screenshots (if relevant)
 
 ## Contribution acceptance criteria
 
 1. The change is as small as possible (see the above paragraph for details)
-1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code)
-1. All tests have to pass, if you suspect a failing CI build is unrelated to your contribution ask for tests to be restarted. See [the CI setup document](http://doc.gitlab.com/ce/development/ci_setup.html) on who you can ask for test restart.
-1. Initially contains a single commit (please use `git rebase -i` to squash commits)
-1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server)
+1. Include proper tests and make all tests pass (unless it contains a test
+   exposing a bug in existing code)
+1. If you suspect a failing CI build is unrelated to your contribution, you may
+   try and restart the failing CI job or ask a developer to fix the
+   aforementioned failing test
+1. Your MR initially contains a single commit (please use `git rebase -i` to
+   squash commits)
+1. Your changes can merge without problems (if not please merge `master`, never
+   rebase commits pushed to the remote server)
 1. Does not break any existing functionality
-1. Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed)
-1. Migrations should do only one thing (eg: either create a table, move data to a new table or remove an old table) to aid retrying on failure
+1. Fixes one specific issue or implements one specific feature (do not combine
+   things, send separate merge requests if needed)
+1. Migrations should do only one thing (eg: either create a table, move data to
+   a new table or remove an old table) to aid retrying on failure
 1. Keeps the GitLab code base clean and well structured
 1. Contains functionality we think other users will benefit from too
 1. Doesn't add configuration options since they complicate future changes
-1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging.
-1. It conforms to the following style guides.
-    If your change touches a line that does not follow the style,
-    modify the entire line to follow it. This prevents linting tools from generating warnings.
-    Don't touch neighbouring lines. As an exception, automatic mass refactoring modifications
-    may leave style non-compliant.
+1. Changes after submitting the merge request should be in separate commits
+   (no squashing). If necessary, you will be asked to squash when the review is
+   over, before merging.
+1. It conforms to the following style guides:
+    * If your change touches a line that does not follow the style, modify the
+      entire line to follow it. This prevents linting tools from generating warnings.
+    * Don't touch neighbouring lines. As an exception, automatic mass
+      refactoring modifications may leave style non-compliant.
 
 ## Style guides
 
 1.  [Ruby](https://github.com/bbatsov/ruby-style-guide).
-    Important sections include [Source Code Layout](https://github.com/bbatsov/ruby-style-guide#source-code-layout)
-    and [Naming](https://github.com/bbatsov/ruby-style-guide#naming). Use:
+    Important sections include [Source Code Layout][rss-source] and
+    [Naming][rss-naming]. Use:
     - multi-line method chaining style **Option B**: dot `.` on previous line
     - string literal quoting style **Option A**: single quoted by default
 1.  [Rails](https://github.com/bbatsov/rails-style-guide)
-1.  [Testing](https://github.com/thoughtbot/guides/tree/master/style#testing)
-1.  [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style#coffeescript)
-1.  [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security
+1.  [Testing](https://github.com/thoughtbot/guides/tree/master/style/testing)
+1.  [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style/coffeescript)
+1.  [Shell commands](doc/development/shell_commands.md) created by GitLab
+    contributors to enhance security
 1.  [Markdown](http://www.cirosantilli.com/markdown-styleguide)
 1.  [Database Migrations](doc/development/migration_style_guide.md)
 1.  [Documentation styleguide](doc_styleguide.md)
-1.  Interface text should be written subjectively instead of objectively. It should be the GitLab core team addressing a person. It should be written in present time and never use past tense (has been/was). For example instead of "prohibited this user from being saved due to the following errors:" the text should be "sorry, we could not create your account because:". Also these [excellent writing guidelines](https://github.com/NARKOZ/guides#writing).
+1.  Interface text should be written subjectively instead of objectively. It
+    should be the GitLab core team addressing a person. It should be written in
+    present time and never use past tense (has been/was). For example instead
+    of _prohibited this user from being saved due to the following errors:_ the
+    text should be _sorry, we could not create your account because:_
 
-This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
+This is also the style used by linting tools such as
+[RuboCop](https://github.com/bbatsov/rubocop),
+[PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
 
 ## Code of conduct
 
-As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
-
-We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
-
-Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
-
-Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
-
-This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
-
-Instances of abusive, harassing, or otherwise unacceptable behavior can be reported by emailing contact@gitlab.com
-
-This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/)
+As contributors and maintainers of this project, we pledge to respect all
+people who contribute through reporting issues, posting feature requests,
+updating documentation, submitting pull requests or patches, and other
+activities.
+
+We are committed to making participation in this project a harassment-free
+experience for everyone, regardless of level of experience, gender, gender
+identity and expression, sexual orientation, disability, personal appearance,
+body size, race, ethnicity, age, or religion.
+
+Examples of unacceptable behavior by participants include the use of sexual
+language or imagery, derogatory comments or personal attacks, trolling, public
+or private harassment, insults, or other unprofessional conduct.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct. Project maintainers who do not
+follow the Code of Conduct may be removed from the project team.
+
+This code of conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior can be
+reported by emailing `contact@gitlab.com`.
+
+This Code of Conduct is adapted from the [Contributor Covenant][], version 1.1.0,
+available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
+
+[core team]: https://about.gitlab.com/core-team/
+[getting help page]: https://about.gitlab.com/getting-help/
+[Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
+[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
+[medium-up-for-grabs]: https://medium.com/@kentcdodds/first-timers-only-78281ea47455
+[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
+[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
+[google-group]: https://groups.google.com/forum/#!forum/gitlabhq
+[stackoverflow]: https://stackoverflow.com/questions/tagged/gitlab
+[fpl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature+proposal
+[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests
+[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Accepting+Merge+Requests
+[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
+[github-mr-tracker]: https://github.com/gitlabhq/gitlabhq/pulls
+[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
+[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
+[closed merge requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
+[definition of done]: http://guide.agilealliance.org/guide/definition-of-done.html
+[Contributor Covenant]: http://contributor-covenant.org
+[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
+[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index e261122d5c4c25447453895b3bcf9b5054ace4a3..d48d3702aed9c6d03de90d13d12199015ce4740e 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-2.6.7
+2.6.9
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 2b7c5ae01848a77d95e2792eb83ab605c9aed91a..4b9fcbec101a6ff8ec68e0f95131ccda4861407f 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.4.2
+0.5.1
diff --git a/Gemfile b/Gemfile
index bd8e9bd8fa10e1f9a6964db61a437f651616eade..2a1c4f7d73a0b2de1fb10c4c153772f4c2148ef4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,10 @@
 source "https://rubygems.org"
 
-gem 'rails', '4.1.14'
+gem 'rails', '4.2.4'
+gem 'rails-deprecated_sanitizer', '~> 1.0.3'
+
+# Responders respond_to and respond_with
+gem 'responders', '~> 2.0'
 
 # Specify a sprockets version due to security issue
 # See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY
@@ -14,11 +18,12 @@ gem "mysql2", '~> 0.3.16', group: :mysql
 gem "pg", '~> 0.18.2', group: :postgres
 
 # Authentication libraries
-gem 'devise',                 '~> 3.5.2'
+gem 'devise',                 '~> 3.5.3'
 gem 'devise-async',           '~> 0.9.0'
-gem 'doorkeeper',             '~> 2.1.3'
+gem 'doorkeeper',             '~> 2.2.0'
 gem 'omniauth',               '~> 1.2.2'
 gem 'omniauth-bitbucket',     '~> 0.0.2'
+gem 'omniauth-cas3',          '~> 1.1.2'
 gem 'omniauth-facebook',      '~> 3.0.0'
 gem 'omniauth-github',        '~> 1.1.1'
 gem 'omniauth-gitlab',        '~> 1.0.0'
@@ -28,7 +33,10 @@ gem 'omniauth-saml',          '~> 1.4.0'
 gem 'omniauth-shibboleth',    '~> 1.2.0'
 gem 'omniauth-twitter',       '~> 1.2.0'
 gem 'omniauth_crowd'
-gem 'rack-oauth2',            '~> 1.0.5'
+gem 'rack-oauth2',            '~> 1.2.1'
+
+# reCAPTCHA protection
+gem 'recaptcha', require: 'recaptcha/rails'
 
 # Two-factor authentication
 gem 'devise-two-factor', '~> 2.0.0'
@@ -48,7 +56,7 @@ gem "gitlab_git", '~> 7.2.20'
 gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap"
 
 # Git Wiki
-gem 'gollum-lib', '~> 4.0.2'
+gem 'gollum-lib', '~> 4.1.0'
 
 # Language detection
 gem "github-linguist", "~> 4.7.0", require: "linguist"
@@ -62,9 +70,6 @@ gem 'rack-cors',    '~> 0.4.0', require: 'rack/cors'
 # based on human-friendly examples
 gem "stamp", '~> 0.6.0'
 
-# Enumeration fields
-gem 'enumerize', '~> 0.7.0'
-
 # Pagination
 gem "kaminari", "~> 0.16.3"
 
@@ -95,9 +100,13 @@ gem 'redcarpet',     '~> 3.3.3'
 gem 'RedCloth',      '~> 4.2.9'
 gem 'rdoc',          '~>3.6'
 gem 'org-ruby',      '~> 0.9.12'
-gem 'creole',        '~>0.3.6'
+gem 'creole',        '~> 0.5.0'
 gem 'wikicloth',     '0.8.1'
 gem 'asciidoctor',   '~> 1.5.2'
+gem 'rouge',         '~> 1.10.1'
+
+# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
+gem 'nokogiri', '1.6.7.1'
 
 # Diffs
 gem 'diffy', '~> 3.0.3'
@@ -118,15 +127,15 @@ gem 'acts-as-taggable-on', '~> 3.4'
 
 # Background jobs
 gem 'sinatra', '~> 1.4.4', require: nil
-gem 'sidekiq', '3.3.0'
-gem 'sidetiq', '~> 0.6.3'
+gem 'sidekiq', '~> 4.0'
+gem 'sidekiq-cron', '~> 0.4.0'
+gem 'redis-namespace'
 
 # HTTP requests
 gem "httparty", '~> 0.13.3'
 
 # Colored output to console
-gem "colored", '~> 1.2'
-gem "colorize", '~> 0.5.8'
+gem "colorize", '~> 0.7.0'
 
 # GitLab settings
 gem 'settingslogic', '~> 2.0.9'
@@ -154,7 +163,7 @@ gem "gemnasium-gitlab-service", "~> 0.2"
 gem "slack-notifier", "~> 1.2.0"
 
 # Asana integration
-gem 'asana', '~> 0.0.6'
+gem 'asana', '~> 0.4.0'
 
 # FogBugz integration
 gem 'ruby-fogbugz', '~> 0.2.1'
@@ -166,13 +175,14 @@ gem 'd3_rails', '~> 3.5.5'
 gem "cal-heatmap-rails", "~> 0.0.1"
 
 # underscore-rails
-gem "underscore-rails", "~> 1.4.4"
+gem "underscore-rails", "~> 1.8.0"
 
 # Sanitize user input
 gem "sanitize", '~> 2.0'
+gem 'babosa', '~> 1.0.2'
 
 # Protect against bruteforcing
-gem "rack-attack", '~> 4.3.0'
+gem "rack-attack", '~> 4.3.1'
 
 # Ace editor
 gem 'ace-rails-ap', '~> 2.0.1'
@@ -183,37 +193,47 @@ gem 'mousetrap-rails', '~> 1.4.6'
 # Detect and convert string character encoding
 gem 'charlock_holmes', '~> 0.7.3'
 
-gem "sass-rails", '~> 4.0.5'
+gem "sass-rails", '~> 5.0.0'
 gem "coffee-rails", '~> 4.1.0'
 gem "uglifier", '~> 2.7.2'
 gem 'turbolinks', '~> 2.5.0'
-gem 'jquery-turbolinks', '~> 2.0.1'
+gem 'jquery-turbolinks', '~> 2.1.0'
 
 gem 'addressable',        '~> 2.3.8'
 gem 'bootstrap-sass',     '~> 3.0'
 gem 'font-awesome-rails', '~> 4.2'
-gem 'gitlab_emoji',       '~> 0.1'
-gem 'gon',                '~> 5.0.0'
+gem 'gitlab_emoji',       '~> 0.2.0'
+gem 'gon',                '~> 6.0.1'
 gem 'jquery-atwho-rails', '~> 1.3.2'
-gem 'jquery-rails',       '~> 3.1.3'
+gem 'jquery-rails',       '~> 4.0.0'
 gem 'jquery-scrollto-rails', '~> 1.4.3'
-gem 'jquery-ui-rails',    '~> 4.2.1'
+gem 'jquery-ui-rails',    '~> 5.0.0'
 gem 'nprogress-rails',    '~> 0.1.6.7'
 gem 'raphael-rails',      '~> 2.1.2'
 gem 'request_store',      '~> 1.2.0'
 gem 'select2-rails',      '~> 3.5.9'
 gem 'virtus',             '~> 1.0.1'
+gem 'net-ssh',            '~> 3.0.1'
+
+# Metrics
+group :metrics do
+  gem 'allocations', '~> 1.0', require: false, platform: :mri
+  gem 'method_source', '~> 0.8', require: false
+  gem 'influxdb', '~> 0.2', require: false
+  gem 'connection_pool', '~> 2.0', require: false
+end
 
 group :development do
   gem "foreman"
-  gem 'brakeman', '3.0.1', require: false
+  gem 'brakeman', '~> 3.1.0', require: false
 
   gem "annotate", "~> 2.6.0"
   gem "letter_opener", '~> 1.1.2'
   gem 'quiet_assets', '~> 1.0.2'
-  gem 'rerun', '~> 0.10.0'
+  gem 'rerun', '~> 0.11.0'
   gem 'bullet', require: false
   gem 'rblineprof', platform: :mri, require: false
+  gem 'web-console', '~> 2.0'
 
   # Better errors handler
   gem 'better_errors', '~> 1.0.1'
@@ -246,7 +266,7 @@ group :development, :test do
 
   gem 'capybara',            '~> 2.4.0'
   gem 'capybara-screenshot', '~> 1.0.0'
-  gem 'poltergeist',         '~> 1.6.0'
+  gem 'poltergeist',         '~> 1.8.1'
 
   gem 'teaspoon', '~> 1.0.0'
   gem 'teaspoon-jasmine', '~> 2.2.0'
@@ -256,7 +276,7 @@ group :development, :test do
   gem 'spring-commands-spinach',  '~> 1.0.0'
   gem 'spring-commands-teaspoon', '~> 0.0.2'
 
-  gem 'rubocop',  '~> 0.28.0',  require: false
+  gem 'rubocop', '~> 0.35.0', require: false
   gem 'coveralls',  '~> 0.8.2', require: false
   gem 'simplecov', '~> 0.10.0', require: false
   gem 'flog', require: false
@@ -270,7 +290,7 @@ group :test do
   gem 'shoulda-matchers', '~> 2.8.0', require: false
   gem 'email_spec', '~> 1.6.0'
   gem 'webmock', '~> 1.21.0'
-  gem 'test_after_commit', '~> 0.2.2'
+  gem 'test_after_commit', '~> 0.4.2'
   gem 'sham_rack'
 end
 
diff --git a/Gemfile.lock b/Gemfile.lock
index 448941cfc8db6e1b4846508f90ec68349b3dadd4..c4cadbafa2658e2e1abecb0c3ba187afe45b226d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,63 +1,72 @@
 GEM
   remote: https://rubygems.org/
   specs:
-    CFPropertyList (2.3.1)
+    CFPropertyList (2.3.2)
     RedCloth (4.2.9)
     ace-rails-ap (2.0.1)
-    actionmailer (4.1.14)
-      actionpack (= 4.1.14)
-      actionview (= 4.1.14)
+    actionmailer (4.2.4)
+      actionpack (= 4.2.4)
+      actionview (= 4.2.4)
+      activejob (= 4.2.4)
       mail (~> 2.5, >= 2.5.4)
-    actionpack (4.1.14)
-      actionview (= 4.1.14)
-      activesupport (= 4.1.14)
-      rack (~> 1.5.2)
+      rails-dom-testing (~> 1.0, >= 1.0.5)
+    actionpack (4.2.4)
+      actionview (= 4.2.4)
+      activesupport (= 4.2.4)
+      rack (~> 1.6)
       rack-test (~> 0.6.2)
-    actionview (4.1.14)
-      activesupport (= 4.1.14)
+      rails-dom-testing (~> 1.0, >= 1.0.5)
+      rails-html-sanitizer (~> 1.0, >= 1.0.2)
+    actionview (4.2.4)
+      activesupport (= 4.2.4)
       builder (~> 3.1)
       erubis (~> 2.7.0)
-    activemodel (4.1.14)
-      activesupport (= 4.1.14)
+      rails-dom-testing (~> 1.0, >= 1.0.5)
+      rails-html-sanitizer (~> 1.0, >= 1.0.2)
+    activejob (4.2.4)
+      activesupport (= 4.2.4)
+      globalid (>= 0.3.0)
+    activemodel (4.2.4)
+      activesupport (= 4.2.4)
       builder (~> 3.1)
-    activerecord (4.1.14)
-      activemodel (= 4.1.14)
-      activesupport (= 4.1.14)
-      arel (~> 5.0.0)
+    activerecord (4.2.4)
+      activemodel (= 4.2.4)
+      activesupport (= 4.2.4)
+      arel (~> 6.0)
     activerecord-deprecated_finders (1.0.4)
-    activerecord-session_store (0.1.1)
+    activerecord-session_store (0.1.2)
       actionpack (>= 4.0.0, < 5)
       activerecord (>= 4.0.0, < 5)
       railties (>= 4.0.0, < 5)
-    activeresource (4.0.0)
-      activemodel (~> 4.0)
-      activesupport (~> 4.0)
-      rails-observers (~> 0.1.1)
-    activesupport (4.1.14)
-      i18n (~> 0.6, >= 0.6.9)
+    activesupport (4.2.4)
+      i18n (~> 0.7)
       json (~> 1.7, >= 1.7.7)
       minitest (~> 5.1)
-      thread_safe (~> 0.1)
+      thread_safe (~> 0.3, >= 0.3.4)
       tzinfo (~> 1.1)
     acts-as-taggable-on (3.5.0)
       activerecord (>= 3.2, < 5)
     addressable (2.3.8)
-    after_commit_queue (1.1.0)
-      rails (>= 3.0)
+    after_commit_queue (1.3.0)
+      activerecord (>= 3.0)
+    allocations (1.0.1)
     annotate (2.6.10)
       activerecord (>= 3.2, <= 4.3)
       rake (~> 10.4)
-    arel (5.0.1.20140414130214)
-    asana (0.0.6)
-      activeresource (>= 3.2.3)
-    asciidoctor (1.5.2)
+    arel (6.0.3)
+    asana (0.4.0)
+      faraday (~> 0.9)
+      faraday_middleware (~> 0.9)
+      faraday_middleware-multi_json (~> 0.0)
+      oauth2 (~> 1.0)
+    asciidoctor (1.5.3)
     ast (2.1.0)
     astrolabe (1.3.1)
       parser (~> 2.2)
     attr_encrypted (1.3.4)
       encryptor (>= 1.3.0)
     attr_required (1.0.0)
-    autoprefixer-rails (5.2.1.2)
+    autoprefixer-rails (6.1.2)
       execjs
       json
     awesome_print (1.2.0)
@@ -65,6 +74,7 @@ GEM
       descendants_tracker (~> 0.0.4)
       ice_nine (~> 0.11.0)
       thread_safe (~> 0.3, >= 0.3.1)
+    babosa (1.0.2)
     bcrypt (3.1.10)
     benchmark-ips (2.3.0)
     better_errors (1.0.1)
@@ -75,25 +85,27 @@ GEM
     bootstrap-sass (3.3.5)
       autoprefixer-rails (>= 5.0.0.1)
       sass (>= 3.2.19)
-    brakeman (3.0.1)
+    brakeman (3.1.4)
       erubis (~> 2.6)
       fastercsv (~> 1.5)
       haml (>= 3.0, < 5.0)
-      highline (~> 1.6.20)
+      highline (>= 1.6.20, < 2.0)
       multi_json (~> 1.2)
-      ruby2ruby (~> 2.1.1)
-      ruby_parser (~> 3.5.0)
+      ruby2ruby (>= 2.1.1, < 2.3.0)
+      ruby_parser (~> 3.7.0)
+      safe_yaml (>= 1.0)
       sass (~> 3.0)
+      slim (>= 1.3.6, < 4.0)
       terminal-table (~> 1.4)
-    browser (1.0.0)
+    browser (1.0.1)
     builder (3.2.2)
-    bullet (4.14.9)
+    bullet (4.14.10)
       activesupport (>= 3.0.0)
       uniform_notifier (~> 1.9.0)
     bundler-audit (0.4.0)
       bundler (~> 1.2)
       thor (~> 0.18)
-    byebug (6.0.2)
+    byebug (8.2.1)
     cal-heatmap-rails (0.0.1)
     capybara (2.4.4)
       mime-types (>= 1.16)
@@ -108,10 +120,9 @@ GEM
       activemodel (>= 3.2.0)
       activesupport (>= 3.2.0)
       json (>= 1.7)
-    celluloid (0.16.0)
-      timers (~> 4.0.0)
+    cause (0.1)
     charlock_holmes (0.7.3)
-    chunky_png (1.3.4)
+    chunky_png (1.3.5)
     cliver (0.3.2)
     coderay (1.1.0)
     coercible (1.0.0)
@@ -122,20 +133,21 @@ GEM
     coffee-script (2.4.1)
       coffee-script-source
       execjs
-    coffee-script-source (1.9.1.1)
-    colored (1.2)
-    colorize (0.5.8)
+    coffee-script-source (1.10.0)
+    colorize (0.7.7)
+    concurrent-ruby (1.0.0)
     connection_pool (2.2.0)
-    coveralls (0.8.2)
+    coveralls (0.8.9)
       json (~> 1.8)
       rest-client (>= 1.6.8, < 2)
       simplecov (~> 0.10.0)
       term-ansicolor (~> 1.3)
       thor (~> 0.19.1)
-    crack (0.4.2)
+      tins (~> 1.6.0)
+    crack (0.4.3)
       safe_yaml (~> 1.0.0)
-    creole (0.3.8)
-    d3_rails (3.5.6)
+    creole (0.5.0)
+    d3_rails (3.5.11)
       railties (>= 3.1.0)
     daemons (1.2.3)
     database_cleaner (1.4.1)
@@ -145,7 +157,7 @@ GEM
       activerecord (>= 3.2.0, < 5.0)
     descendants_tracker (0.0.4)
       thread_safe (~> 0.3, >= 0.3.1)
-    devise (3.5.2)
+    devise (3.5.3)
       bcrypt (~> 3.0)
       orm_adapter (~> 0.1)
       railties (>= 3.2.6, < 5)
@@ -154,7 +166,7 @@ GEM
       warden (~> 1.2.3)
     devise-async (0.9.0)
       devise (~> 3.2)
-    devise-two-factor (2.0.0)
+    devise-two-factor (2.0.1)
       activesupport
       attr_encrypted (~> 1.3.2)
       devise (~> 3.5.0)
@@ -163,19 +175,17 @@ GEM
     diff-lcs (1.2.5)
     diffy (3.0.7)
     docile (1.1.5)
-    domain_name (0.5.24)
+    domain_name (0.5.25)
       unf (>= 0.0.5, < 1.0.0)
-    doorkeeper (2.1.4)
+    doorkeeper (2.2.2)
       railties (>= 3.2)
-    dropzonejs-rails (0.7.1)
+    dropzonejs-rails (0.7.2)
       rails (> 3.1)
     email_reply_parser (0.5.8)
     email_spec (1.6.0)
       launchy (~> 2.1)
       mail (~> 2.2)
     encryptor (1.3.0)
-    enumerize (0.7.0)
-      activesupport (>= 3.2)
     equalizer (0.0.11)
     erubis (2.7.0)
     escape_utils (1.1.0)
@@ -192,6 +202,9 @@ GEM
       multipart-post (>= 1.2, < 3)
     faraday_middleware (0.10.0)
       faraday (>= 0.7.4, < 0.10)
+    faraday_middleware-multi_json (0.0.6)
+      faraday_middleware
+      multi_json
     fastercsv (1.5.5)
     ffaker (2.0.0)
     ffi (1.9.10)
@@ -203,7 +216,7 @@ GEM
     flog (4.3.2)
       ruby_parser (~> 3.1, > 3.1.0)
       sexp_processor (~> 4.4)
-    flowdock (0.7.0)
+    flowdock (0.7.1)
       httparty (~> 0.7)
       multi_json
     fog (1.25.0)
@@ -221,17 +234,14 @@ GEM
       ipaddress (~> 0.5)
       nokogiri (~> 1.5, >= 1.5.11)
       opennebula
-    fog-brightbox (0.9.0)
+    fog-brightbox (0.10.1)
       fog-core (~> 1.22)
       fog-json
       inflecto (~> 0.0.2)
-    fog-core (1.32.1)
+    fog-core (1.35.0)
       builder
       excon (~> 0.45)
       formatador (~> 0.2)
-      mime-types
-      net-scp (~> 1.1)
-      net-ssh (>= 2.1.3)
     fog-json (1.0.2)
       fog-core (~> 1.0)
       multi_json (~> 1.10)
@@ -243,10 +253,10 @@ GEM
       fog-core (>= 1.21.0)
       fog-json
       fog-xml (>= 0.0.1)
-    fog-sakuracloud (1.0.1)
+    fog-sakuracloud (1.5.0)
       fog-core
       fog-json
-    fog-softlayer (0.4.7)
+    fog-softlayer (1.0.2)
       fog-core
       fog-json
     fog-terremark (0.1.0)
@@ -261,7 +271,7 @@ GEM
     fog-xml (0.1.2)
       fog-core
       nokogiri (~> 1.5, >= 1.5.11)
-    font-awesome-rails (4.4.0.0)
+    font-awesome-rails (4.5.0.0)
       railties (>= 3.2, < 5.0)
     foreman (0.78.0)
       thor (~> 0.19.1)
@@ -271,11 +281,11 @@ GEM
       ruby-progressbar (~> 1.4)
     gemnasium-gitlab-service (0.2.6)
       rugged (~> 0.21)
-    gemojione (2.0.1)
+    gemojione (2.1.1)
       json
     get_process_mem (0.2.0)
     gherkin-ruby (0.3.2)
-    github-linguist (4.7.0)
+    github-linguist (4.7.3)
       charlock_holmes (~> 0.7.3)
       escape_utils (~> 1.1.0)
       mime-types (>= 1.19)
@@ -290,9 +300,9 @@ GEM
       diff-lcs (~> 1.1)
       mime-types (~> 1.15)
       posix-spawn (~> 0.3)
-    gitlab_emoji (0.1.1)
-      gemojione (~> 2.0)
-    gitlab_git (7.2.20)
+    gitlab_emoji (0.2.0)
+      gemojione (~> 2.1)
+    gitlab_git (7.2.22)
       activesupport (~> 4.0)
       charlock_holmes (~> 0.7.3)
       github-linguist (~> 4.7.0)
@@ -303,18 +313,22 @@ GEM
       omniauth (~> 1.0)
       pyu-ruby-sasl (~> 0.0.3.1)
       rubyntlm (~> 0.3)
+    globalid (0.3.6)
+      activesupport (>= 4.1.0)
     gollum-grit_adapter (1.0.0)
       gitlab-grit (~> 2.7, >= 2.7.1)
-    gollum-lib (4.0.3)
+    gollum-lib (4.1.0)
       github-markup (~> 1.3.3)
       gollum-grit_adapter (~> 1.0)
       nokogiri (~> 1.6.4)
-      rouge (~> 1.10.1)
+      rouge (~> 1.9)
       sanitize (~> 2.1.0)
       stringex (~> 2.5.1)
-    gon (5.0.4)
-      actionpack (>= 2.3.0)
+    gon (6.0.1)
+      actionpack (>= 3.0)
       json
+      multi_json
+      request_store (>= 1.0)
     grape (0.13.0)
       activesupport
       builder
@@ -336,13 +350,12 @@ GEM
       haml (>= 4.0.6, < 5.0)
       html2haml (>= 1.0.1)
       railties (>= 4.0.1)
-    hashie (3.4.2)
-    highline (1.6.21)
+    hashie (3.4.3)
+    highline (1.7.8)
     hike (1.2.3)
     hipchat (1.5.2)
       httparty
       mimemagic
-    hitimes (1.2.3)
     html-pipeline (1.11.0)
       activesupport (>= 2)
       nokogiri (~> 1.4)
@@ -354,40 +367,44 @@ GEM
     http-cookie (1.0.2)
       domain_name (~> 0.5)
     http_parser.rb (0.5.3)
-    httparty (0.13.5)
+    httparty (0.13.7)
       json (~> 1.8)
       multi_xml (>= 0.5.2)
-    httpclient (2.6.0.1)
+    httpclient (2.7.0.1)
     i18n (0.7.0)
-    ice_cube (0.11.1)
     ice_nine (0.11.1)
     inflecto (0.0.2)
+    influxdb (0.2.3)
+      cause
+      json
     ipaddress (0.8.0)
     jquery-atwho-rails (1.3.2)
-    jquery-rails (3.1.3)
-      railties (>= 3.0, < 5.0)
+    jquery-rails (4.0.5)
+      rails-dom-testing (~> 1.0)
+      railties (>= 4.2.0)
       thor (>= 0.14, < 2.0)
     jquery-scrollto-rails (1.4.3)
       railties (> 3.1, < 5.0)
-    jquery-turbolinks (2.0.2)
+    jquery-turbolinks (2.1.0)
       railties (>= 3.1.0)
       turbolinks
-    jquery-ui-rails (4.2.1)
+    jquery-ui-rails (5.0.5)
       railties (>= 3.2.16)
     json (1.8.3)
-    jwt (1.5.1)
+    jwt (1.5.2)
     kaminari (0.16.3)
       actionpack (>= 3.0.0)
       activesupport (>= 3.0.0)
-    kgio (2.9.3)
+    kgio (2.10.0)
     launchy (2.4.3)
       addressable (~> 2.3)
     letter_opener (1.1.2)
       launchy (~> 2.2)
-    listen (2.10.1)
-      celluloid (~> 0.16.0)
+    listen (3.0.5)
       rb-fsevent (>= 0.9.3)
       rb-inotify (>= 0.9)
+    loofah (2.0.3)
+      nokogiri (>= 1.5.9)
     macaddr (1.7.1)
       systemu (~> 2.6.2)
     mail (2.6.3)
@@ -396,7 +413,7 @@ GEM
     method_source (0.8.2)
     mime-types (1.25.1)
     mimemagic (0.3.0)
-    mini_portile (0.6.2)
+    mini_portile2 (2.0.0)
     minitest (5.7.0)
     mousetrap-rails (1.4.6)
     multi_json (1.11.2)
@@ -404,17 +421,15 @@ GEM
     multipart-post (2.0.0)
     mysql2 (0.3.20)
     nested_form (0.3.2)
-    net-ldap (0.11)
-    net-scp (1.2.1)
-      net-ssh (>= 2.6.5)
-    net-ssh (2.9.2)
-    netrc (0.10.3)
-    newrelic-grape (2.0.0)
+    net-ldap (0.12.1)
+    net-ssh (3.0.1)
+    netrc (0.11.0)
+    newrelic-grape (2.1.0)
       grape
       newrelic_rpm
     newrelic_rpm (3.9.4.245)
-    nokogiri (1.6.6.2)
-      mini_portile (~> 0.6.0)
+    nokogiri (1.6.7.1)
+      mini_portile2 (~> 2.0.0.rc2)
     nprogress-rails (0.1.6.7)
     oauth (0.4.7)
     oauth2 (1.0.0)
@@ -432,17 +447,24 @@ GEM
       multi_json (~> 1.7)
       omniauth (~> 1.1)
       omniauth-oauth (~> 1.0)
+    omniauth-cas3 (1.1.3)
+      addressable (~> 2.3)
+      nokogiri (~> 1.6.6)
+      omniauth (~> 1.2)
     omniauth-facebook (3.0.0)
       omniauth-oauth2 (~> 1.2)
     omniauth-github (1.1.2)
       omniauth (~> 1.0)
       omniauth-oauth2 (~> 1.1)
-    omniauth-gitlab (1.0.0)
+    omniauth-gitlab (1.0.1)
       omniauth (~> 1.0)
       omniauth-oauth2 (~> 1.0)
-    omniauth-google-oauth2 (0.2.6)
-      omniauth (> 1.0)
-      omniauth-oauth2 (~> 1.1)
+    omniauth-google-oauth2 (0.2.10)
+      addressable (~> 2.3)
+      jwt (~> 1.0)
+      multi_json (~> 1.3)
+      omniauth (>= 1.1.1)
+      omniauth-oauth2 (~> 1.3.1)
     omniauth-kerberos (0.3.0)
       omniauth-multipassword
       timfel-krb5-auth (~> 0.8)
@@ -466,26 +488,26 @@ GEM
       activesupport
       nokogiri (>= 1.4.4)
       omniauth (~> 1.0)
-    opennebula (4.12.1)
+    opennebula (4.14.2)
       json
       nokogiri
       rbvmomi
     org-ruby (0.9.12)
       rubypants (~> 0.2)
     orm_adapter (0.5.0)
-    paranoia (2.1.3)
+    paranoia (2.1.4)
       activerecord (~> 4.0)
-    parser (2.2.2.6)
+    parser (2.2.3.0)
       ast (>= 1.1, < 3.0)
-    pg (0.18.2)
-    poltergeist (1.6.0)
+    pg (0.18.4)
+    poltergeist (1.8.1)
       capybara (~> 2.1)
       cliver (~> 0.3.1)
       multi_json (~> 1.0)
       websocket-driver (>= 0.2.0)
     posix-spawn (0.3.11)
-    powerpack (0.0.9)
-    pry (0.10.1)
+    powerpack (0.1.1)
+    pry (0.10.3)
       coderay (~> 1.1.0)
       method_source (~> 0.8.1)
       slop (~> 3.4)
@@ -494,15 +516,15 @@ GEM
     pyu-ruby-sasl (0.0.3.3)
     quiet_assets (1.0.3)
       railties (>= 3.1, < 5.0)
-    rack (1.5.5)
+    rack (1.6.4)
     rack-accept (0.4.5)
       rack (>= 0.4)
-    rack-attack (4.3.0)
+    rack-attack (4.3.1)
       rack
     rack-cors (0.4.0)
     rack-mount (0.8.3)
       rack (>= 1.0.0)
-    rack-oauth2 (1.0.10)
+    rack-oauth2 (1.2.1)
       activesupport (>= 2.3)
       attr_required (>= 0.0.5)
       httpclient (>= 2.4)
@@ -512,28 +534,35 @@ GEM
       rack
     rack-test (0.6.3)
       rack (>= 1.0)
-    rails (4.1.14)
-      actionmailer (= 4.1.14)
-      actionpack (= 4.1.14)
-      actionview (= 4.1.14)
-      activemodel (= 4.1.14)
-      activerecord (= 4.1.14)
-      activesupport (= 4.1.14)
+    rails (4.2.4)
+      actionmailer (= 4.2.4)
+      actionpack (= 4.2.4)
+      actionview (= 4.2.4)
+      activejob (= 4.2.4)
+      activemodel (= 4.2.4)
+      activerecord (= 4.2.4)
+      activesupport (= 4.2.4)
       bundler (>= 1.3.0, < 2.0)
-      railties (= 4.1.14)
-      sprockets-rails (~> 2.0)
-    rails-observers (0.1.2)
-      activemodel (~> 4.0)
-    railties (4.1.14)
-      actionpack (= 4.1.14)
-      activesupport (= 4.1.14)
+      railties (= 4.2.4)
+      sprockets-rails
+    rails-deprecated_sanitizer (1.0.3)
+      activesupport (>= 4.2.0.alpha)
+    rails-dom-testing (1.0.7)
+      activesupport (>= 4.2.0.beta, < 5.0)
+      nokogiri (~> 1.6.0)
+      rails-deprecated_sanitizer (>= 1.0.1)
+    rails-html-sanitizer (1.0.2)
+      loofah (~> 2.0)
+    railties (4.2.4)
+      actionpack (= 4.2.4)
+      activesupport (= 4.2.4)
       rake (>= 0.8.7)
       thor (>= 0.18.1, < 2.0)
     rainbow (2.0.0)
     raindrops (0.15.0)
     rake (10.4.2)
     raphael-rails (2.1.2)
-    rb-fsevent (0.9.5)
+    rb-fsevent (0.9.6)
     rb-inotify (0.9.5)
       ffi (>= 0.5.0)
     rblineprof (0.3.6)
@@ -544,14 +573,16 @@ GEM
       trollop
     rdoc (3.12.2)
       json (~> 1.4)
+    recaptcha (1.0.2)
+      json
     redcarpet (3.3.3)
-    redis (3.2.1)
-    redis-actionpack (4.0.0)
+    redis (3.2.2)
+    redis-actionpack (4.0.1)
       actionpack (~> 4)
       redis-rack (~> 1.5.0)
       redis-store (~> 1.1.0)
-    redis-activesupport (4.1.1)
-      activesupport (~> 4)
+    redis-activesupport (4.1.5)
+      activesupport (>= 3, < 5)
       redis-store (~> 1.1.0)
     redis-namespace (1.5.2)
       redis (~> 3.0, >= 3.0.4)
@@ -562,13 +593,13 @@ GEM
       redis-actionpack (~> 4)
       redis-activesupport (~> 4)
       redis-store (~> 1.1.0)
-    redis-store (1.1.6)
+    redis-store (1.1.7)
       redis (>= 2.2)
-    request_store (1.2.0)
-    rerun (0.10.0)
-      listen (~> 2.7, >= 2.7.3)
-    responders (1.1.2)
-      railties (>= 3.2, < 4.2)
+    request_store (1.2.1)
+    rerun (0.11.0)
+      listen (~> 3.0)
+    responders (2.1.0)
+      railties (>= 4.2.0, < 5)
     rest-client (1.8.0)
       http-cookie (>= 1.0.2, < 2.0)
       mime-types (>= 1.16, < 3.0)
@@ -601,35 +632,38 @@ GEM
       rspec-mocks (~> 3.3.0)
       rspec-support (~> 3.3.0)
     rspec-support (3.3.0)
-    rubocop (0.28.0)
+    rubocop (0.35.1)
       astrolabe (~> 1.3)
-      parser (>= 2.2.0.pre.7, < 3.0)
-      powerpack (~> 0.0.6)
+      parser (>= 2.2.3.0, < 3.0)
+      powerpack (~> 0.1)
       rainbow (>= 1.99.1, < 3.0)
-      ruby-progressbar (~> 1.4)
+      ruby-progressbar (~> 1.7)
+      tins (<= 1.6.0)
     ruby-fogbugz (0.2.1)
       crack (~> 0.4)
     ruby-progressbar (1.7.5)
     ruby-saml (1.0.0)
       nokogiri (>= 1.5.10)
       uuid (~> 2.3)
-    ruby2ruby (2.1.4)
+    ruby2ruby (2.2.0)
       ruby_parser (~> 3.1)
       sexp_processor (~> 4.0)
-    ruby_parser (3.5.0)
+    ruby_parser (3.7.2)
       sexp_processor (~> 4.1)
     rubyntlm (0.5.2)
     rubypants (0.2.0)
+    rufus-scheduler (3.1.10)
     rugged (0.23.3)
     safe_yaml (1.0.4)
     sanitize (2.1.0)
       nokogiri (>= 1.4.4)
-    sass (3.2.19)
-    sass-rails (4.0.5)
+    sass (3.4.20)
+    sass-rails (5.0.4)
       railties (>= 4.0.0, < 5.0)
-      sass (~> 3.2.2)
-      sprockets (~> 2.8, < 3.0)
-      sprockets-rails (~> 2.0)
+      sass (~> 3.1)
+      sprockets (>= 2.8, < 4.0)
+      sprockets-rails (>= 2.0, < 4.0)
+      tilt (>= 1.1, < 3)
     sawyer (0.6.0)
       addressable (~> 2.3.5)
       faraday (~> 0.8, < 0.10)
@@ -647,16 +681,15 @@ GEM
       rack
     shoulda-matchers (2.8.0)
       activesupport (>= 3.0.0)
-    sidekiq (3.3.0)
-      celluloid (>= 0.16.0)
-      connection_pool (>= 2.0.0)
-      json
-      redis (>= 3.0.6)
-      redis-namespace (>= 1.3.1)
-    sidetiq (0.6.3)
-      celluloid (>= 0.14.1)
-      ice_cube (= 0.11.1)
-      sidekiq (>= 3.0.0)
+    sidekiq (4.0.1)
+      concurrent-ruby (~> 1.0)
+      connection_pool (~> 2.2, >= 2.2.0)
+      json (~> 1.0)
+      redis (~> 3.2, >= 3.2.1)
+    sidekiq-cron (0.4.0)
+      redis-namespace (>= 1.5.2)
+      rufus-scheduler (>= 2.0.24)
+      sidekiq (>= 4.0.0)
     simple_oauth (0.1.9)
     simplecov (0.10.0)
       docile (~> 1.1.0)
@@ -669,6 +702,9 @@ GEM
       tilt (>= 1.3, < 3)
     six (0.2.0)
     slack-notifier (1.2.1)
+    slim (3.0.6)
+      temple (~> 0.7.3)
+      tilt (>= 1.3.3, < 2.1)
     slop (3.6.0)
     spinach (0.8.10)
       colorize
@@ -710,20 +746,19 @@ GEM
       railties (>= 3.2.5, < 5)
     teaspoon-jasmine (2.2.0)
       teaspoon (>= 1.0.0)
+    temple (0.7.6)
     term-ansicolor (1.3.2)
       tins (~> 1.0)
     terminal-table (1.5.2)
-    test_after_commit (0.2.7)
+    test_after_commit (0.4.2)
       activerecord (>= 3.2)
-    thin (1.6.3)
+    thin (1.6.4)
       daemons (~> 1.0, >= 1.0.9)
-      eventmachine (~> 1.0)
+      eventmachine (~> 1.0, >= 1.0.4)
       rack (~> 1.0)
     thor (0.19.1)
     thread_safe (0.3.5)
     tilt (1.4.1)
-    timers (4.0.4)
-      hitimes
     timfel-krb5-auth (0.8.3)
     tinder (1.10.1)
       eventmachine (~> 1.0)
@@ -747,7 +782,7 @@ GEM
     uglifier (2.7.2)
       execjs (>= 0.3.0)
       json (>= 1.8.0)
-    underscore-rails (1.4.4)
+    underscore-rails (1.8.3)
     unf (0.1.4)
       unf_ext
     unf_ext (0.0.7.1)
@@ -755,9 +790,9 @@ GEM
       kgio (~> 2.6)
       rack
       raindrops (~> 0.7)
-    unicorn-worker-killer (0.4.3)
+    unicorn-worker-killer (0.4.4)
       get_process_mem (~> 0)
-      unicorn (~> 4)
+      unicorn (>= 4, < 6)
     uniform_notifier (1.9.0)
     uuid (2.3.8)
       macaddr (~> 1.0)
@@ -767,12 +802,17 @@ GEM
       coercible (~> 1.0)
       descendants_tracker (~> 0.0, >= 0.0.3)
       equalizer (~> 0.0, >= 0.0.9)
-    warden (1.2.3)
+    warden (1.2.4)
       rack (>= 1.0)
+    web-console (2.2.1)
+      activemodel (>= 4.0)
+      binding_of_caller (>= 0.7.2)
+      railties (>= 4.0)
+      sprockets-rails (>= 2.0, < 4.0)
     webmock (1.21.0)
       addressable (>= 2.3.6)
       crack (>= 0.3.2)
-    websocket-driver (0.6.2)
+    websocket-driver (0.6.3)
       websocket-extensions (>= 0.1.0)
     websocket-extensions (0.1.2)
     wikicloth (0.8.1)
@@ -793,16 +833,18 @@ DEPENDENCIES
   acts-as-taggable-on (~> 3.4)
   addressable (~> 2.3.8)
   after_commit_queue
+  allocations (~> 1.0)
   annotate (~> 2.6.0)
-  asana (~> 0.0.6)
+  asana (~> 0.4.0)
   asciidoctor (~> 1.5.2)
   attr_encrypted (~> 1.3.4)
   awesome_print (~> 1.2.0)
+  babosa (~> 1.0.2)
   benchmark-ips
   better_errors (~> 1.0.1)
   binding_of_caller (~> 0.7.2)
   bootstrap-sass (~> 3.0)
-  brakeman (= 3.0.1)
+  brakeman (~> 3.1.0)
   browser (~> 1.0.0)
   bullet
   bundler-audit
@@ -813,22 +855,21 @@ DEPENDENCIES
   carrierwave (~> 0.9.0)
   charlock_holmes (~> 0.7.3)
   coffee-rails (~> 4.1.0)
-  colored (~> 1.2)
-  colorize (~> 0.5.8)
+  colorize (~> 0.7.0)
+  connection_pool (~> 2.0)
   coveralls (~> 0.8.2)
-  creole (~> 0.3.6)
+  creole (~> 0.5.0)
   d3_rails (~> 3.5.5)
   database_cleaner (~> 1.4.0)
   default_value_for (~> 3.0.0)
-  devise (~> 3.5.2)
+  devise (~> 3.5.3)
   devise-async (~> 0.9.0)
   devise-two-factor (~> 2.0.0)
   diffy (~> 3.0.3)
-  doorkeeper (~> 2.1.3)
+  doorkeeper (~> 2.2.0)
   dropzonejs-rails (~> 0.7.1)
   email_reply_parser (~> 0.5.8)
   email_spec (~> 1.6.0)
-  enumerize (~> 0.7.0)
   factory_girl_rails (~> 4.3.0)
   ffaker (~> 2.0.0)
   flay
@@ -841,37 +882,42 @@ DEPENDENCIES
   github-linguist (~> 4.7.0)
   github-markup (~> 1.3.1)
   gitlab-flowdock-git-hook (~> 1.0.1)
-  gitlab_emoji (~> 0.1)
+  gitlab_emoji (~> 0.2.0)
   gitlab_git (~> 7.2.20)
   gitlab_meta (= 7.0)
   gitlab_omniauth-ldap (~> 1.2.1)
-  gollum-lib (~> 4.0.2)
-  gon (~> 5.0.0)
+  gollum-lib (~> 4.1.0)
+  gon (~> 6.0.1)
   grape (~> 0.13.0)
   grape-entity (~> 0.4.2)
   haml-rails (~> 0.9.0)
   hipchat (~> 1.5.0)
   html-pipeline (~> 1.11.0)
   httparty (~> 0.13.3)
+  influxdb (~> 0.2)
   jquery-atwho-rails (~> 1.3.2)
-  jquery-rails (~> 3.1.3)
+  jquery-rails (~> 4.0.0)
   jquery-scrollto-rails (~> 1.4.3)
-  jquery-turbolinks (~> 2.0.1)
-  jquery-ui-rails (~> 4.2.1)
+  jquery-turbolinks (~> 2.1.0)
+  jquery-ui-rails (~> 5.0.0)
   kaminari (~> 0.16.3)
   letter_opener (~> 1.1.2)
   mail_room (~> 0.6.1)
+  method_source (~> 0.8)
   minitest (~> 5.7.0)
   mousetrap-rails (~> 1.4.6)
   mysql2 (~> 0.3.16)
   nested_form (~> 0.3.2)
+  net-ssh (~> 3.0.1)
   newrelic-grape
   newrelic_rpm (~> 3.9.4.245)
+  nokogiri (= 1.6.7.1)
   nprogress-rails (~> 0.1.6.7)
   oauth2 (~> 1.0.0)
   octokit (~> 3.7.0)
   omniauth (~> 1.2.2)
   omniauth-bitbucket (~> 0.0.2)
+  omniauth-cas3 (~> 1.1.2)
   omniauth-facebook (~> 3.0.0)
   omniauth-github (~> 1.1.1)
   omniauth-gitlab (~> 1.0.0)
@@ -884,34 +930,39 @@ DEPENDENCIES
   org-ruby (~> 0.9.12)
   paranoia (~> 2.0)
   pg (~> 0.18.2)
-  poltergeist (~> 1.6.0)
+  poltergeist (~> 1.8.1)
   pry-rails
   quiet_assets (~> 1.0.2)
-  rack-attack (~> 4.3.0)
+  rack-attack (~> 4.3.1)
   rack-cors (~> 0.4.0)
-  rack-oauth2 (~> 1.0.5)
-  rails (= 4.1.14)
+  rack-oauth2 (~> 1.2.1)
+  rails (= 4.2.4)
+  rails-deprecated_sanitizer (~> 1.0.3)
   raphael-rails (~> 2.1.2)
   rblineprof
   rdoc (~> 3.6)
+  recaptcha
   redcarpet (~> 3.3.3)
+  redis-namespace
   redis-rails (~> 4.0.0)
   request_store (~> 1.2.0)
-  rerun (~> 0.10.0)
+  rerun (~> 0.11.0)
+  responders (~> 2.0)
+  rouge (~> 1.10.1)
   rqrcode-rails3 (~> 0.1.7)
   rspec-rails (~> 3.3.0)
-  rubocop (~> 0.28.0)
+  rubocop (~> 0.35.0)
   ruby-fogbugz (~> 0.2.1)
   sanitize (~> 2.0)
-  sass-rails (~> 4.0.5)
+  sass-rails (~> 5.0.0)
   sdoc (~> 0.3.20)
   seed-fu (~> 2.3.5)
   select2-rails (~> 3.5.9)
   settingslogic (~> 2.0.9)
   sham_rack
   shoulda-matchers (~> 2.8.0)
-  sidekiq (= 3.3.0)
-  sidetiq (~> 0.6.3)
+  sidekiq (~> 4.0)
+  sidekiq-cron (~> 0.4.0)
   simplecov (~> 0.10.0)
   sinatra (~> 1.4.4)
   six (~> 0.2.0)
@@ -927,17 +978,18 @@ DEPENDENCIES
   task_list (~> 1.0.2)
   teaspoon (~> 1.0.0)
   teaspoon-jasmine (~> 2.2.0)
-  test_after_commit (~> 0.2.2)
+  test_after_commit (~> 0.4.2)
   thin (~> 1.6.1)
   tinder (~> 1.10.0)
   turbolinks (~> 2.5.0)
   uglifier (~> 2.7.2)
-  underscore-rails (~> 1.4.4)
+  underscore-rails (~> 1.8.0)
   unf (~> 0.1.4)
   unicorn (~> 4.8.2)
   unicorn-worker-killer (~> 0.4.2)
   version_sorter (~> 2.0.0)
   virtus (~> 1.0.1)
+  web-console (~> 2.0)
   webmock (~> 1.21.0)
   wikicloth (= 0.8.1)
 
diff --git a/PROCESS.md b/PROCESS.md
index 482ad5fe9e1aae5d7007a690bf032de5d1023e84..5f4d67bc10e72f26e62ac7761725a7672b3dbce7 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -8,7 +8,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
 
 ### Issue team
 - Looks for issues without [workflow labels](#how-we-handle-issues) and triages issue
-- Closes invalid issues with a comment (duplicates, [feature requests](#feature-requests), [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.)
+- Closes invalid issues with a comment (duplicates, [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.)
 - Asks for feedback from issue reporter ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.)
 - Monitors all issues for feedback (but especially ones commented on since automatically watching them)
 - Closes issues with no feedback from the reporter for two weeks
@@ -44,6 +44,9 @@ Workflow labels are purposely not very detailed since that would be hard to keep
 - *UX* needs needs help from a UX designer
 - *Frontend* needs help from a Front-end engineer
 - *Graphics* needs help from a Graphics designer
+- *up-for-grabs* is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels.
+- *feature proposal* is a proposal for a new feature for GitLab. People are encouraged to vote
+in support or comment for further detail. Do not use `feature request`.
 
 Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label.
 
@@ -61,7 +64,6 @@ If an issue is complex and needs the attention of a specific person, assignment
 -   Bright orange `#eb6420`: workflow labels for core team members (attached MR, awaiting developer action/feedback)
 -   Light blue `#82C5FF`: functional labels
 -   Green labels `#009800`: issues that can generally be ignored. For example, issues given the following labels normally can be closed immediately:
-    - Feature request (see copy & paste response: [Feature requests](#feature-requests))
     - Support (see copy & paste response: [Support requests and configuration questions](#support-requests-and-configuration-questions)
 
 ## Be kind
@@ -74,10 +76,6 @@ Be kind to people trying to contribute. Be aware that people may be a non-native
 
 Thanks for the issue report. Please reformat your issue to conform to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
 
-### Feature requests
-
-Thank you for your interest in improving GitLab. We don't use the issue tracker for feature requests. Things that are wrong but are not a regression compared to older versions of GitLab are considered feature requests and not issues. Please use the \[feature request forum\]\(http://feedback.gitlab.com/) for this purpose or create a merge request implementing this feature. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
-
 ### Issue report for old version
 
 Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
@@ -112,7 +110,12 @@ This merge request has been closed because a request for more information has no
 
 ### Accepting merge requests
 
-Is there a request on [the feature request forum](http://feedback.gitlab.com/forums/176466-general) that is similar to this? If so, can you make a comment with a link to it? Please be aware that new functionality that is not marked [accepting merge/pull requests](http://feedback.gitlab.com/forums/176466-general/status/796455) on the forum might not make it into GitLab. You might be asked to make changes and even after implementing them your feature might still be declined. If you want to reduce the chance of this happening please have a discussion in the forum first.
+Is there an issue on the [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues)
+that is similar to this?
+Could you please link it here?
+Please be aware that new functionality that is not marked
+[accepting merge requests](https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests)
+might not make it into GitLab.
 
 ### Only accepting merge requests with green tests
 
diff --git a/Procfile b/Procfile
index 08880b9c425aa2af24dc07bd904b72bfd4084b4c..9cfdee7040fb72406291e7c8e9cd0d180f7f2456 100644
--- a/Procfile
+++ b/Procfile
@@ -1,3 +1,7 @@
+# For DEVELOPMENT only. Production uses Runit in
+# https://gitlab.com/gitlab-org/omnibus-gitlab or the init scripts in
+# lib/support/init.d, which call scripts in bin/ .
+#
 web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
-worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default
+worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default
 # mail_room: bundle exec mail_room -q -c config/mail_room.yml
diff --git a/README.md b/README.md
index c59c8593eba16e7c818b5a6b56c316a224a7e286..3ec1d4a776cb68afff4a253fcd1d0f5b08c7964b 100644
--- a/README.md
+++ b/README.md
@@ -69,7 +69,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
 - Ubuntu/Debian/CentOS/RHEL
 - Ruby (MRI) 2.1
 - Git 1.7.10+
-- Redis 2.4+
+- Redis 2.8+
 - MySQL or PostgreSQL
 
 For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html).
@@ -80,7 +80,7 @@ There are a lot of [third-party applications integrating with GitLab](https://ab
 
 ## GitLab release cycle
 
-Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases are published when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
+For more information about the release process see the [release documentation](http://doc.gitlab.com/ce/release/).
 
 ## Upgrading
 
diff --git a/VERSION b/VERSION
index 8d0676ff07ba5372063bb36af9529a8d976c16ad..ce669730119df83f8d693ec9e7401baf55b9245a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.3.0.pre
+8.4.0.pre
diff --git a/app/assets/images/brand_logo.png b/app/assets/images/brand_logo.png
deleted file mode 100644
index 9c564bb61411bf4f0dec3e21aa86848ac70cbcb8..0000000000000000000000000000000000000000
Binary files a/app/assets/images/brand_logo.png and /dev/null differ
diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png
new file mode 100644
index 0000000000000000000000000000000000000000..a8ad7b6eab612af71df106bf8ac26541a92fe217
Binary files /dev/null and b/app/assets/images/emoji.png differ
diff --git a/app/assets/images/gitlab_logo.png b/app/assets/images/gitlab_logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..0c157546b9cf87d9aaf9a404c01f8a623f7c24c7
Binary files /dev/null and b/app/assets/images/gitlab_logo.png differ
diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png
index 60021d5ac47686cce70570d22bad6670082caf56..7d89da97e11a0030fbab56b3d172f547ac9f896e 100644
Binary files a/app/assets/images/icon-link.png and b/app/assets/images/icon-link.png differ
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index 9e5d594c861d6867308d9c47b571795fe5f51efd..746fa3cea871322fe7f5b42d15197107391bab39 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -2,6 +2,8 @@
   groups_path: "/api/:version/groups.json"
   group_path: "/api/:version/groups/:id.json"
   namespaces_path: "/api/:version/namespaces.json"
+  group_projects_path: "/api/:version/groups/:id/projects.json"
+  projects_path: "/api/:version/projects.json"
 
   group: (group_id, callback) ->
     url = Api.buildUrl(Api.group_path)
@@ -44,6 +46,35 @@
     ).done (namespaces) ->
       callback(namespaces)
 
+  # Return projects list. Filtered by query
+  projects: (query, callback) ->
+    url = Api.buildUrl(Api.projects_path)
+
+    $.ajax(
+      url: url
+      data:
+        private_token: gon.api_token
+        search: query
+        per_page: 20
+      dataType: "json"
+    ).done (projects) ->
+      callback(projects)
+
+  # Return group projects list. Filtered by query
+  groupProjects: (group_id, query, callback) ->
+    url = Api.buildUrl(Api.group_projects_path)
+    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)
+
   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.coffee b/app/assets/javascripts/application.js.coffee
index 945ffb660e66783325f44014185f1604b502c480..affab5bb030d8c72eba0fb7295f700cca0aecf1a 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -5,7 +5,7 @@
 # the compiled file.
 #
 #= require jquery
-#= require jquery.ui.all
+#= require jquery-ui
 #= require jquery_ujs
 #= require jquery.cookie
 #= require jquery.endless-scroll
@@ -135,17 +135,25 @@ $ ->
     ), 1
 
   # Initialize tooltips
-  $('body').tooltip({
-    selector: '.has_tooltip, [data-toggle="tooltip"], .page-sidebar-collapsed .nav-sidebar a'
+  $('body').tooltip(
+    selector: '.has_tooltip, [data-toggle="tooltip"]'
     placement: (_, el) ->
       $el = $(el)
-      if $el.attr('id') == 'js-shortcuts-home'
-        # Place the logo tooltip on the right when collapsed, bottom when expanded
-        $el.parents('header').hasClass('header-collapsed') and 'right' or 'bottom'
-      else
-        # Otherwise use the data-placement attribute, or 'bottom' if undefined
-        $el.data('placement') or 'bottom'
-  })
+      $el.data('placement') || 'bottom'
+  )
+
+  $('.header-logo .home').tooltip(
+    placement: (_, el) ->
+      $el = $(el)
+      if $('.page-with-sidebar').hasClass('page-sidebar-collapsed') then 'right' else 'bottom'
+    container: 'body'
+  )
+
+  $('.page-with-sidebar').tooltip(
+    selector: '.sidebar-collapsed .nav-sidebar a, .sidebar-collapsed a.sidebar-user'
+    placement: 'right'
+    container: 'body'
+  )
 
   # Form submitter
   $('.trigger-submit').on 'change', ->
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 09b48fe5572ab09b4bc97430d3d6a6f5ce846866..619abb1fb071367785e20e80b975d8c4199a8af4 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,11 +1,29 @@
 class @AwardsHandler
-  constructor: (@post_emoji_url, @noteable_type, @noteable_id) ->
+  constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
+    $(".add-award").click (event)->
+      event.stopPropagation()
+      event.preventDefault()
+      $(".emoji-menu").show()
+
+    $("html").click ->
+      if !$(event.target).closest(".emoji-menu").length
+        if $(".emoji-menu").is(":visible")
+          $(".emoji-menu").hide()
+
+    @renderFrequentlyUsedBlock()
+    @setupSearch()
 
   addAward: (emoji) ->
+    emoji = @normilizeEmojiName(emoji)
     @postEmoji emoji, =>
       @addAwardToEmojiBar(emoji)
+
+    $(".emoji-menu").hide()
     
-  addAwardToEmojiBar: (emoji, custom_path = '') ->
+  addAwardToEmojiBar: (emoji) ->
+    @addEmojiToFrequentlyUsedList(emoji)
+
+    emoji = @normilizeEmojiName(emoji)
     if @exist(emoji)
       if @isActive(emoji)
         @decrementCounter(emoji)
@@ -15,7 +33,7 @@ class @AwardsHandler
         counter.parent().addClass("active")
         @addMeToAuthorList(emoji)
     else
-      @createEmoji(emoji, custom_path)
+      @createEmoji(emoji)
 
   exist: (emoji) ->
     @findEmojiIcon(emoji).length > 0
@@ -25,15 +43,19 @@ class @AwardsHandler
 
   decrementCounter: (emoji) ->
     counter = @findEmojiIcon(emoji).siblings(".counter")
+    emojiIcon = counter.parent()
 
     if parseInt(counter.text()) > 1
       counter.text(parseInt(counter.text()) - 1)
-      counter.parent().removeClass("active")
+      emojiIcon.removeClass("active")
       @removeMeFromAuthorList(emoji)
+    else if emoji =="thumbsup" || emoji == "thumbsdown"
+      emojiIcon.tooltip("destroy")
+      counter.text(0)
+      emojiIcon.removeClass("active")
     else
-      award = counter.parent()
-      award.tooltip("destroy")
-      award.remove()
+      emojiIcon.tooltip("destroy")
+      emojiIcon.remove()
 
   removeMeFromAuthorList: (emoji) ->
     award_block = @findEmojiIcon(emoji).parent()
@@ -52,35 +74,39 @@ class @AwardsHandler
   resetTooltip: (award) ->
     award.tooltip("destroy")
 
-    # "destroy" call is asynchronous, this is why we need to set timeout.
+    # "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
     setTimeout (->
       award.tooltip()
     ), 200
     
 
-  createEmoji: (emoji, custom_path) ->
+  createEmoji: (emoji) ->
+    emojiCssClass = @resolveNameToCssClass(emoji)
+
     nodes = []
     nodes.push("<div class='award active' title='me'>")
-    nodes.push("<div class='icon' data-emoji='" + emoji + "'>")
-    nodes.push(@getImage(emoji, custom_path))
+    nodes.push("<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>")
+    nodes.push("<div class='counter'>1</div>")
     nodes.push("</div>")
-    nodes.push("<div class='counter'>1")
-    nodes.push("</div></div>")
 
-    $(".awards-controls").before(nodes.join("\n"))
+    emoji_node = $(nodes.join("\n")).insertBefore(".awards-controls").find(".emoji-icon").data("emoji", emoji)
 
     $(".award").tooltip()
 
-  getImage: (emoji, custom_path) ->
-    if custom_path
-      $("<img>").attr({src: custom_path, width: 20, height: 20}).wrap("<div>").parent().html()
+  resolveNameToCssClass: (emoji) ->
+    emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']")
+
+    if emoji_icon.length > 0
+      unicodeName = emoji_icon.data("unicode-name")
     else
-      $("li[data-emoji='" + emoji + "']").html()
+      # Find by alias
+      unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data("unicode-name")
 
+    "emoji-#{unicodeName}"
 
   postEmoji: (emoji, callback) ->
     $.post @post_emoji_url, { note: {
-      note: ":" + emoji + ":"
+      note: ":#{emoji}:"
       noteable_type: @noteable_type
       noteable_id: @noteable_id
     }},(data) ->
@@ -88,4 +114,53 @@ class @AwardsHandler
         callback.call()
 
   findEmojiIcon: (emoji) ->
-    $(".icon[data-emoji='" + emoji + "']")
\ No newline at end of file
+    $(".award [data-emoji='#{emoji}']")
+
+  scrollToAwards: ->
+    $('body, html').animate({
+      scrollTop: $('.awards').offset().top - 80
+    }, 200)
+
+  normilizeEmojiName: (emoji) ->
+    @aliases[emoji] || emoji
+
+  addEmojiToFrequentlyUsedList: (emoji) ->
+    frequently_used_emojis = @getFrequentlyUsedEmojis()
+    frequently_used_emojis.push(emoji)
+    $.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 })
+
+  getFrequentlyUsedEmojis: ->
+    frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",")
+    _.compact(_.uniq(frequently_used_emojis))
+
+  renderFrequentlyUsedBlock: ->
+    if $.cookie('frequently_used_emojis')
+      frequently_used_emojis = @getFrequentlyUsedEmojis()
+
+      ul = $("<ul>")
+
+      for emoji in frequently_used_emojis
+        do (emoji) ->
+          $(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul)
+
+      $("input.emoji-search").after(ul).after($("<h5>").text("Frequently used"))
+
+  setupSearch: ->
+    $("input.emoji-search").keyup (ev) =>
+      term = $(ev.target).val()
+
+      # Clean previous search results
+      $("ul.emoji-search,h5.emoji-search").remove()
+
+      if term
+        # Generate a search result block
+        h5 = $("<h5>").text("Search results").addClass("emoji-search")
+        found_emojis = @searchEmojis(term).show()
+        ul = $("<ul>").addClass("emoji-search").append(found_emojis)
+        $(".emoji-menu-content ul, .emoji-menu-content h5").hide()
+        $(".emoji-menu-content").append(h5).append(ul)
+      else
+        $(".emoji-menu-content").children().show()
+
+  searchEmojis: (term)->
+    $(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone()
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
index 195f8b11e5dbac896379cd30d13f3caa3532e130..9df932817f6aa785360c5b0a973f3d3895369be7 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
@@ -35,7 +35,7 @@ class @BlobFileDropzone
           return
 
         this.on 'sending', (file, xhr, formData) ->
-          formData.append('new_branch', form.find('.js-new-branch').val())
+          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
diff --git a/app/assets/javascripts/copy_to_clipboard.js.coffee b/app/assets/javascripts/copy_to_clipboard.js.coffee
index 9c68c5cc1bcdf377edf3a958c7dc5ffc2dc50554..24301e01b10de7394f68498b8f1c1627274acba3 100644
--- a/app/assets/javascripts/copy_to_clipboard.js.coffee
+++ b/app/assets/javascripts/copy_to_clipboard.js.coffee
@@ -1,32 +1,37 @@
 #= require clipboard
 
-$ ->
-  clipboard = new Clipboard '.js-clipboard-trigger',
-    text: (trigger) ->
-      $target = $(trigger.nextElementSibling || trigger.previousElementSibling)
-      $target.data('clipboard-text') || $target.text().trim()
+genericSuccess = (e) ->
+  showTooltip(e.trigger, 'Copied!')
+
+  # Clear the selection and blur the trigger so it loses its border
+  e.clearSelection()
+  $(e.trigger).blur()
 
-  clipboard.on 'success', (e) ->
-    $(e.trigger).
-      tooltip(trigger: 'manual', placement: 'auto bottom', title: 'Copied!').
-      tooltip('show').
-      one('mouseleave', -> $(this).tooltip('hide'))
+# 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'
 
-    # Clear the selection and blur the trigger so it loses its border
-    e.clearSelection()
-    $(e.trigger).blur()
+  showTooltip(e.trigger, "Press #{key}-C to copy")
 
-  # Safari doesn't support `execCommand`, so instead we inform the user to
-  # copy manually.
-  #
-  # See http://clipboardjs.com/#browser-support
-  clipboard.on 'error', (e) ->
-    if /Mac/i.test(navigator.userAgent)
-      title = "Press &#8984;-C to copy"
-    else
-      title = "Press Ctrl-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'))
 
-    $(e.trigger).
-      tooltip(trigger: 'manual', placement: 'auto bottom', html: true, title: title).
-      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/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 4059fc39c67b5d20c2c58e7cfb59b4048f8b836e..69e061ce6e927813b489eea7eee06ac7c29e8c4a 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -49,7 +49,7 @@ class Dispatcher
         new DropzoneInput($('.release-form'))
       when 'projects:merge_requests:show'
         new Diff()
-        shortcut_handler = new ShortcutsIssuable()
+        shortcut_handler = new ShortcutsIssuable(true)
         new ZenMode()
       when "projects:merge_requests:diffs"
         new Diff()
@@ -83,7 +83,7 @@ class Dispatcher
       when 'projects:project_members:index'
         new ProjectMembers()
         new UsersSelect()
-      when 'groups:new', 'groups:edit', 'admin:groups:edit'
+      when 'groups:new', 'groups:edit', 'admin:groups:edit', 'admin:groups:new'
         new GroupAvatar()
       when 'projects:tree:show'
         new TreeView()
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index 6f789e668af49e5650e21091479d7a8e3c4f3f9f..30a35a04339f6a9fcc680e0ff554a32ea0443275 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -1,3 +1,5 @@
+#= require markdown_preview
+
 class @DropzoneInput
   constructor: (form) ->
     Dropzone.autoDiscover = false
@@ -11,17 +13,14 @@ class @DropzoneInput
     uploadProgress = $("<div class=\"div-dropzone-progress\"></div>")
     btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
     project_uploads_path = window.project_uploads_path or null
-    markdown_preview_path = window.markdown_preview_path or null
     max_file_size = gon.max_file_size or 10
 
     form_textarea = $(form).find("textarea.markdown-area")
     form_textarea.wrap "<div class=\"div-dropzone\"></div>"
     form_textarea.on 'paste', (event) =>
       handlePaste(event)
-    form_textarea.on "input", ->
-      hideReferencedUsers()
-    form_textarea.on "blur", ->
-      renderMarkdown()
+
+    $(form).setupMarkdownPreview()
 
     form_dropzone = $(form).find('.div-dropzone')
     form_dropzone.parent().addClass "div-dropzone-wrapper"
@@ -34,42 +33,6 @@ class @DropzoneInput
       "opacity": 0
       "display": "none"
 
-    # Preview button
-    $(document).off "click", ".js-md-preview-button"
-    $(document).on "click", ".js-md-preview-button", (e) ->
-      ###
-      Shows the Markdown preview.
-
-      Lets the server render GFM into Html and displays it.
-      ###
-      e.preventDefault()
-      form = $(this).closest("form")
-      # toggle tabs
-      form.find(".js-md-write-button").parent().removeClass "active"
-      form.find(".js-md-preview-button").parent().addClass "active"
-
-      # toggle content
-      form.find(".md-write-holder").hide()
-      form.find(".md-preview-holder").show()
-
-      renderMarkdown()
-
-    # Write button
-    $(document).off "click", ".js-md-write-button"
-    $(document).on "click", ".js-md-write-button", (e) ->
-      ###
-      Shows the Markdown textarea.
-      ###
-      e.preventDefault()
-      form = $(this).closest("form")
-      # toggle tabs
-      form.find(".js-md-write-button").parent().addClass "active"
-      form.find(".js-md-preview-button").parent().removeClass "active"
-
-      # toggle content
-      form.find(".md-write-holder").show()
-      form.find(".md-preview-holder").hide()
-
     dropzone = form_dropzone.dropzone(
       url: project_uploads_path
       dictDefaultMessage: ""
@@ -136,41 +99,6 @@ class @DropzoneInput
 
     child = $(dropzone[0]).children("textarea")
 
-    hideReferencedUsers = ->
-      referencedUsers = form.find(".referenced-users")
-      referencedUsers.hide()
-
-    renderReferencedUsers = (users) ->
-      referencedUsers = form.find(".referenced-users")
-
-      if referencedUsers.length
-        if users.length >= 10
-          referencedUsers.show()
-          referencedUsers.find(".js-referenced-users-count").text users.length
-        else
-          referencedUsers.hide()
-
-    renderMarkdown = ->
-      preview = form.find(".js-md-preview")
-      mdText = form.find(".markdown-area").val()
-      if mdText.trim().length is 0
-        preview.text "Nothing to preview."
-        hideReferencedUsers()
-      else
-        preview.text "Loading..."
-        $.ajax(
-          type: "POST",
-          url: markdown_preview_path,
-          data: {
-            text: mdText
-          },
-          dataType: "json"
-        ).success (data) ->
-          preview.html data.body
-          preview.syntaxHighlight()
-
-          renderReferencedUsers data.references.users
-
     formatLink = (link) ->
       text = "[#{link.alt}](#{link.url})"
       text = "!#{text}" if link.is_image
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
index b39ab0c447580cbd7f5ef80f38f395da8f1bba1b..5de012e409f26d1a7dbf90069dd8c61cb2ecc201 100644
--- a/app/assets/javascripts/flash.js.coffee
+++ b/app/assets/javascripts/flash.js.coffee
@@ -1,12 +1,16 @@
 class @Flash
   constructor: (message, type)->
-    flash = $(".flash-container")
-    flash.html("")
+    @flash = $(".flash-container")
+    @flash.html("")
 
-    $('<div/>',
+    innerDiv = $('<div/>',
       class: "flash-#{type}",
       text: message
-    ).appendTo(".flash-container")
+    )
+    innerDiv.appendTo(".flash-container")
 
-    flash.click -> $(@).fadeOut()
-    flash.show()
+    @flash.click -> $(@).fadeOut()
+    @flash.show()
+
+  pinTo: (selector) ->
+    @flash.detach().appendTo(selector)
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index c4d3e619f5eb3fe88ae8d254cea14341b54e9864..02232698bc20ea522d4a47c414b691b496092d24 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -5,9 +5,9 @@ class @IssuableContext
     new UsersSelect()
     $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
 
-    $(".context .inline-update").on "change", "select", ->
+    $(".issuable-sidebar .inline-update").on "change", "select", ->
       $(this).submit()
-    $(".context .inline-update").on "change", ".js-assignee", ->
+    $(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
       $(this).submit()
 
     $('.issuable-details').waitForImages ->
@@ -21,3 +21,9 @@ class @IssuableContext
           @top = ($('.issuable-affix').offset().top - 70)
         bottom: ->
           @bottom = $('.footer').outerHeight(true)
+
+    $(".edit-link").click (e) ->
+      block = $(@).parents('.block')
+      block.find('.selectbox').show()
+      block.find('.value').hide()
+      block.find('.js-select2').select2("open")
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 603a16da1ce645d51f2958fa68437e91653ebb65..c256ec8f41bb178189c5e39ce1a589d312a05dd8 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -1,3 +1,4 @@
+#= require flash
 #= require jquery.waitforimages
 #= require task_list
 
@@ -6,16 +7,47 @@ class @Issue
     # Prevent duplicate event bindings
     @disableTaskList()
 
-    if $("a.btn-close").length
+    if $('a.btn-close').length
       @initTaskList()
+      @initIssueBtnEventListeners()
 
   initTaskList: ->
-    $('.issue-details .js-task-list-container').taskList('enable')
-    $(document).on 'tasklist:changed', '.issue-details .js-task-list-container', @updateTaskList
+    $('.detail-page-description .js-task-list-container').taskList('enable')
+    $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
+
+  initIssueBtnEventListeners: ->
+    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')
+      $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 data.saved
+            $this.addClass('hidden')
+            if isClose
+              $('a.btn-reopen').removeClass('hidden')
+              $('div.status-box-closed').removeClass('hidden')
+              $('div.status-box-open').addClass('hidden')
+            else
+              $('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)
 
   disableTaskList: ->
-    $('.issue-details .js-task-list-container').taskList('disable')
-    $(document).off 'tasklist:changed', '.issue-details .js-task-list-container'
+    $('.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
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index 40bb9e9cb0cc45f1d79d96959b0635ba91379986..ac9e022e727870e7344aad984b2bae632cbdd7de 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -29,7 +29,7 @@
     $('#filter_issue_search').val($('#issue_search').val())
 
   initSelects: ->
-    $("select#update_status").select2(width: 'resolve', dropdownAutoWidth: true)
+    $("select#update_state_event").select2(width: 'resolve', dropdownAutoWidth: true)
     $("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true)
     $("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true)
     $("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true)
diff --git a/app/assets/javascripts/markdown_preview.js.coffee b/app/assets/javascripts/markdown_preview.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..98fc8f173401e10487494ad5c025de363baed0dd
--- /dev/null
+++ b/app/assets/javascripts/markdown_preview.js.coffee
@@ -0,0 +1,87 @@
+# 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
+
+  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
+
+    $.ajax
+      type: 'POST'
+      url: window.markdown_preview_path
+      data: { text: text }
+      dataType: 'json'
+      success: success
+
+  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'
+
+$.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 'click', previewButtonSelector, (e) ->
+  e.preventDefault()
+
+  $form = $(this).closest('form')
+
+  # 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 'click', writeButtonSelector, (e) ->
+  e.preventDefault()
+
+  $form = $(this).closest('form')
+
+  # toggle tabs
+  $form.find(writeButtonSelector).parent().addClass('active')
+  $form.find(previewButtonSelector).parent().removeClass('active')
+
+  # toggle content
+  $form.find('.md-write-holder').show()
+  $form.find('.md-preview-holder').hide()
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index b21cb7904b56fdd99739d81c6458ce556af6b414..9047587db81af96a3aeca6a6451b64851a3cd4ba 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -40,12 +40,12 @@ class @MergeRequest
     this.$('.all-commits').removeClass 'hide'
 
   initTaskList: ->
-    $('.merge-request-details .js-task-list-container').taskList('enable')
-    $(document).on 'tasklist:changed', '.merge-request-details .js-task-list-container', @updateTaskList
+    $('.detail-page-description .js-task-list-container').taskList('enable')
+    $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
 
   disableTaskList: ->
-    $('.merge-request-details .js-task-list-container').taskList('disable')
-    $(document).off 'tasklist:changed', '.merge-request-details .js-task-list-container'
+    $('.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
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 593a8f42130d560598656814753d48df5fde3cde..9e2dc1250c9fa556029b9fbf1e6c7f0dc94d13ee 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -43,6 +43,7 @@
 #
 class @MergeRequestTabs
   diffsLoaded: false
+  buildsLoaded: false
   commitsLoaded: false
 
   constructor: (@opts = {}) ->
@@ -54,6 +55,12 @@ class @MergeRequestTabs
 
   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)
@@ -63,12 +70,14 @@ class @MergeRequestTabs
       @loadCommits($target.attr('href'))
     else if action == 'diffs'
       @loadDiff($target.attr('href'))
+    else if action == 'builds'
+      @loadBuilds($target.attr('href'))
 
     @setCurrentAction(action)
 
   scrollToElement: (container) ->
     if window.location.hash
-      $el = $("#{container} #{window.location.hash}")
+      $el = $("div#{container} #{window.location.hash}")
       $('body').scrollTo($el.offset().top) if $el.length
 
   # Activate a tab based on the current action
@@ -101,7 +110,7 @@ class @MergeRequestTabs
     action = 'notes' if action == 'show'
 
     # Remove a trailing '/commits' or '/diffs'
-    new_state = @_location.pathname.replace(/\/(commits|diffs)(\.html)?\/?$/, '')
+    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'
@@ -124,7 +133,7 @@ class @MergeRequestTabs
     @_get
       url: "#{source}.json"
       success: (data) =>
-        document.getElementById('commits').innerHTML = data.html
+        document.querySelector("div#commits").innerHTML = data.html
         $('.js-timeago').timeago()
         @commitsLoaded = true
         @scrollToElement("#commits")
@@ -135,10 +144,22 @@ class @MergeRequestTabs
     @_get
       url: "#{source}.json" + @_location.search
       success: (data) =>
-        document.getElementById('diffs').innerHTML = data.html
+        document.querySelector("div#diffs").innerHTML = data.html
+        $('div#diffs .js-syntax-highlight').syntaxHighlight()
         @diffsLoaded = true
         @scrollToElement("#diffs")
 
+  loadBuilds: (source) ->
+    return if @buildsLoaded
+
+    @_get
+      url: "#{source}.json"
+      success: (data) =>
+        document.querySelector("div#builds").innerHTML = data.html
+        $('.js-timeago').timeago()
+        @buildsLoaded = true
+        @scrollToElement("#builds")
+
   # Show or hide the loading spinner
   #
   # status - Boolean, true to show, false to hide
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index 3176e5a89652b72256342fc822bc485efeaaa780..738ffc8343bfd34437915c56f7148ec2cbc8f5cc 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -10,17 +10,20 @@ class @MergeRequestWidget
   constructor: (@opts) ->
     modal = $('#modal_merge_info').modal(show: false)
 
-  mergeInProgress: ->
+  mergeInProgress: (deleteSourceBranch = false)->
     $.ajax
       type: 'GET'
       url: $('.merge-request').data('url')
       success: (data) =>
         if data.state == "merged"
-          location.reload()
+          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
-          setTimeout(merge_request_widget.mergeInProgress, 2000)
+          callback = -> merge_request_widget.mergeInProgress(deleteSourceBranch)
+          setTimeout(callback, 2000)
       dataType: 'json'
 
   getMergeStatus: ->
diff --git a/app/assets/javascripts/new_branch_form.js.coffee b/app/assets/javascripts/new_branch_form.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..4b350854f7855d438dc3a06d6c5ce202c9ed91af
--- /dev/null
+++ b/app/assets/javascripts/new_branch_form.js.coffee
@@ -0,0 +1,78 @@
+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.coffee b/app/assets/javascripts/new_commit_form.js.coffee
index 2e561dea3e1eafe8a52f0bba807d36d8040d2fec..03f0f51acfad536ba0927ceaf5541f774796e0dd 100644
--- a/app/assets/javascripts/new_commit_form.js.coffee
+++ b/app/assets/javascripts/new_commit_form.js.coffee
@@ -1,9 +1,9 @@
 class @NewCommitForm
   constructor: (form) ->
-    @newBranch = form.find('.js-new-branch')
+    @newBranch = form.find('.js-target-branch')
     @originalBranch = form.find('.js-original-branch')
     @createMergeRequest = form.find('.js-create-merge-request')
-    @createMergeRequestFormGroup = form.find('.js-create-merge-request-form-group')
+    @createMergeRequestContainer = form.find('.js-create-merge-request-container')
 
     @renderDestination()
     @newBranch.keyup @renderDestination
@@ -12,10 +12,10 @@ class @NewCommitForm
     different = @newBranch.val() != @originalBranch.val()
 
     if different
-      @createMergeRequestFormGroup.show()
+      @createMergeRequestContainer.show()
       @createMergeRequest.prop('checked', true) unless @wasDifferent
     else
-      @createMergeRequestFormGroup.hide()
+      @createMergeRequestContainer.hide()
       @createMergeRequest.prop('checked', false)
 
     @wasDifferent = different
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 7de7632201ddc10da38369321aac4ef08434e267..9e5204bfeebb7b474ad935e64a3eb59e46d2daa2 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -111,6 +111,12 @@ class @Notes
   Note: for rendering inline notes use renderDiscussionNote
   ###
   renderNote: (note) ->
+    unless note.valid
+      if note.award
+        flash = new Flash('You have already used this award emoji!', 'alert')
+        flash.pinTo('.header-content')
+      return
+
     # render note if it not present in loaded list
     # or skip if rendered
     if @isNewNote(note) && !note.award
@@ -121,7 +127,8 @@ class @Notes
       @initTaskList()
 
     if note.award
-      awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
+      awards_handler.addAwardToEmojiBar(note.note)
+      awards_handler.scrollToAwards()
 
   ###
   Check if note does not exists on page
@@ -141,6 +148,8 @@ class @Notes
     @note_ids.push(note.id)
     form = $("form[rel='" + note.discussion_id + "']")
     row = form.closest("tr")
+    note_html = $(note.html)
+    note_html.syntaxHighlight()
 
     # is this the first note of discussion?
     if row.is(".js-temp-notes-holder")
@@ -151,14 +160,16 @@ class @Notes
       row.next().find(".note").remove()
 
       # Add note to 'Changes' page discussions
-      $(".notes[rel='" + note.discussion_id + "']").append note.html
+      $(".notes[rel='" + note.discussion_id + "']").append note_html
 
       # Init discussion on 'Discussion' page if it is merge request page
       if $('body').attr('data-page').indexOf('projects:merge_request') == 0
-        $('ul.main-notes-list').append(note.discussion_with_diff_html)
+        discussion_html = $(note.discussion_with_diff_html)
+        discussion_html.syntaxHighlight()
+        $('ul.main-notes-list').append(discussion_html)
     else
       # append new note to all matching discussions
-      $(".notes[rel='" + note.discussion_id + "']").append note.html
+      $(".notes[rel='" + note.discussion_id + "']").append note_html
 
     # cleanup after successfully creating a diff/discussion note
     @removeDiscussionNoteForm(form)
@@ -279,7 +290,7 @@ class @Notes
     $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_#{note.id}")
+    $note_li = $('.note-row-' + note.id)
     $note_li.replaceWith($html)
 
   ###
@@ -339,18 +350,26 @@ class @Notes
   ###
   removeNote: ->
     note = $(this).closest(".note")
-    notes = note.closest(".notes")
+    note_id = note.attr('id')
+
+    $('.note[id="' + note_id + '"]').each ->
+      note = $(this)
+      notes = note.closest(".notes")
+      count = notes.closest(".notes_holder").find(".discussion-notes-count")
 
-    # check if this is the last note for this line
-    if notes.find(".note").length is 1
+      # check if this is the last note for this line
+      if notes.find(".note").length is 1
 
-      # for discussions
-      notes.closest(".discussion").remove()
+        # for discussions
+        notes.closest(".discussion").remove()
 
-      # for diff lines
-      notes.closest("tr").remove()
+        # for diff lines
+        notes.closest("tr").remove()
+      else
+        # update notes count
+        count.get(0).lastChild.nodeValue = " #{notes.children().length - 1}"
 
-    note.remove()
+      note.remove()
 
   ###
   Called in response to clicking the delete attachment link
@@ -362,8 +381,8 @@ class @Notes
     note = $(this).closest(".note")
     note.find(".note-attachment").remove()
     note.find(".note-body > .note-text").show()
-    note.find(".js-note-attachment-delete").hide()
-    note.find(".note-edit-form").hide()
+    note.find(".note-header").show()
+    note.find(".current-note-edit-form").remove()
 
   ###
   Called when clicking on the "reply" button for a diff line.
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index 0ea8fffce07179b69b4e81352ce47ef39dc86c1c..d7a658f8faa4dd0ce23db69fef1de40d01ec137b 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -1,13 +1,23 @@
 class @Project
   constructor: ->
-    # Git clone panel switcher
-    cloneHolder = $('.git-clone-holder')
-    if cloneHolder.length
-      $('a, button', cloneHolder).click ->
-        $('a, button', cloneHolder).removeClass 'active'
-        $(@).addClass 'active'
-        $('#project_clone', cloneHolder).val $(@).data 'clone'
-        $(".clone").text("").append $(@).data 'clone'
+    # 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()
+      console.log("url",url)
+
+      # Update the input field
+      $('#project_clone').val(url)
+
+      # Update the command line instructions
+      $('.clone').text(url)
 
     # Ref switcher
     $('.project-refs-select').on 'change', ->
@@ -39,4 +49,4 @@ class @Project
         when 4 then label = ' On Mention '
       $('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
       $(@).parents('ul').find('li.active').removeClass 'active'
-      $(@).parent().addClass 'active'
\ No newline at end of file
+      $(@).parent().addClass 'active'
diff --git a/app/assets/javascripts/project_select.js.coffee b/app/assets/javascripts/project_select.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..0ae274f33632dc71ca5862ec68c18971b94d9525
--- /dev/null
+++ b/app/assets/javascripts/project_select.js.coffee
@@ -0,0 +1,39 @@
+class @ProjectSelect
+  constructor: ->
+    $('.ajax-project-select').each (i, select) ->
+      @groupId = $(select).data('group-id')
+      @includeGroups = $(select).data('include-groups')
+
+      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, projectsCallback
+
+        id: (project) ->
+          project.web_url
+
+        text: (project) ->
+          project.name_with_namespace || project.name
+
+        dropdownCssClass: "ajax-project-dropdown"
diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee
index db5faf71faf79461144bdd260d9bb3adbf380c0b..f2887af190bc5a9e4093786822c0e51138a881e1 100644
--- a/app/assets/javascripts/projects_list.js.coffee
+++ b/app/assets/javascripts/projects_list.js.coffee
@@ -8,17 +8,17 @@ class @ProjectsList
 
     $(".projects-list-filter").keyup ->
       terms = $(this).val()
-      uiBox = $(this).closest('.projects-list-holder')
+      uiBox = $('div.projects-list-holder')
       if terms == "" || terms == undefined
-        uiBox.find(".projects-list li").show()
+        uiBox.find("ul.projects-list li").show()
       else
-        uiBox.find(".projects-list li").each (index) ->
-          name = $(this).find(".filter-title").text()
+        uiBox.find("ul.projects-list li").each (index) ->
+          name = $(this).find("span.filter-title").text()
 
           if name.toLowerCase().search(terms.toLowerCase()) == -1
             $(this).hide()
           else
             $(this).show()
-      uiBox.find(".projects-list li.bottom").hide()
+      uiBox.find("ul.projects-list li.bottom").hide()
 
 
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
index e9aeb1e9525ccd00e733b625178e5b2fcb8fa84a..4d915bfc8c5c3e83b8d2bfd94659050ed2d0edcd 100644
--- a/app/assets/javascripts/shortcuts.js.coffee
+++ b/app/assets/javascripts/shortcuts.js.coffee
@@ -7,7 +7,7 @@ class @Shortcuts
 
   selectiveHelp: (e) =>
     Shortcuts.showHelp(e, @enabledHelp)
-      
+
   @showHelp: (e, location) ->
     if $('#modal-shortcuts').length > 0
       $('#modal-shortcuts').modal('show')
@@ -17,8 +17,7 @@ class @Shortcuts
         dataType: 'script',
         success: (e) ->
           if location and location.length > 0
-            for l in location
-              $(l).show()
+            $(l).show() for l in location
           else
             $('.hidden-shortcut').show()
             $('.js-more-help-button').remove()
@@ -28,3 +27,8 @@ class @Shortcuts
   @focusSearch: (e) ->
     $('#search').focus()
     e.preventDefault()
+
+$(document).on 'click.more_help', '.js-more-help-button', (e) ->
+  $(@).remove()
+  $('.hidden-shortcut').show()
+  e.preventDefault()
diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee
index fb08016fbae35335289b15ee08a69d131c9b0cd5..ae59480af9e6762dd8698cb7872f05eb56f8725a 100644
--- a/app/assets/javascripts/sidebar.js.coffee
+++ b/app/assets/javascripts/sidebar.js.coffee
@@ -5,6 +5,7 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
 
   $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
   $('header').toggleClass("header-collapsed header-expanded")
+  $('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded")
   $('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
   $.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
 )
diff --git a/app/assets/javascripts/star.js.coffee b/app/assets/javascripts/star.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..d849b2e79500adbf730f3bee7a2ab1fb0ec18527
--- /dev/null
+++ b/app/assets/javascripts/star.js.coffee
@@ -0,0 +1,22 @@
+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('span.count').text data.star_count
+        if isStarred
+          $starSpan.removeClass('starred').text 'Star'
+          $starIcon.removeClass('fa-star').addClass 'fa-star-o'
+        else
+          $starSpan.addClass('starred').text 'Unstar'
+          $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
\ No newline at end of file
diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee
index d0d81f96921ac16630670b69567ae2ec61cee224..ec4271b092cf124d17172114560f397d5dfdcba6 100644
--- a/app/assets/javascripts/user.js.coffee
+++ b/app/assets/javascripts/user.js.coffee
@@ -2,3 +2,9 @@ class @User
   constructor: ->
     $('.profile-groups-avatars').tooltip("placement": "top")
     new ProjectsList()
+
+    $('.hide-project-limit-message').on 'click', (e) ->
+      path = '/'
+      $.cookie('hide_project_limit_message', 'false', { path: path })
+      $(@).parents('.project-limit-message').remove()
+      e.preventDefault()
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index f5db74d84e786fa9b6757b1ef57ab7e2bb98afd6..12abf806bfab11ec03a633b8526c3e1e112f203a 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -32,17 +32,15 @@ class @UsersSelect
               if showNullUser
                 nullUser = {
                   name: 'Unassigned',
-                  avatar: null,
-                  username: 'none',
                   id: 0
                 }
                 data.results.unshift(nullUser)
 
               if showAnyUser
+                name = showAnyUser
+                name = 'Any User' if name == true
                 anyUser = {
-                  name: 'Any',
-                  avatar: null,
-                  username: 'none',
+                  name: name,
                   id: null
                 }
                 data.results.unshift(anyUser)
@@ -50,7 +48,6 @@ class @UsersSelect
             if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/)
               emailUser = {
                 name: "Invite \"#{query.term}\"",
-                avatar: null,
                 username: query.term,
                 id: query.term
               }
@@ -82,10 +79,10 @@ class @UsersSelect
     else
       avatar = gon.default_avatar_url
 
-    "<div class='user-result'>
+    "<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 class='user-username'>#{user.username || ""}</div>
      </div>"
 
   formatSelection: (user) ->
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 7b060ce48532c36dadd52950c5f8f293a7515821..0c0451fe4ddffcca924242dc13424067609af4a0 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -2,8 +2,8 @@
  * This is a manifest file that'll automatically include all the stylesheets available in this directory
  * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
  * the top of the compiled file, but it's generally better to create a new file per style scope.
- *= require jquery.ui.datepicker
- *= require jquery.ui.autocomplete
+ *= require jquery-ui/datepicker
+ *= require jquery-ui/autocomplete
  *= require jquery.atwho
  *= require select2
  *= require_self
@@ -48,4 +48,4 @@
 /*
  * Styles for JS behaviors.
  */
-@import "behaviors.scss";
\ No newline at end of file
+@import "behaviors.scss";
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 1ec9d2fd84f1fe0e5c69104c7a33c7564f085458..48a4971c8fca834044693063d2ac031688122d0e 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -1,9 +1,9 @@
 @import "framework/fonts";
 @import "framework/variables";
 @import "framework/mixins";
-@import "framework/layout";
 @import 'framework/tw_bootstrap_variables';
 @import 'framework/tw_bootstrap';
+@import "framework/layout";
 
 @import "framework/avatar.scss";
 @import "framework/blocks.scss";
@@ -25,6 +25,7 @@
 @import "framework/markdown_area.scss";
 @import "framework/mobile.scss";
 @import "framework/pagination.scss";
+@import "framework/panels.scss";
 @import "framework/selects.scss";
 @import "framework/sidebar.scss";
 @import "framework/tables.scss";
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 1635df9c97b3081be3dcb1d7d1d742604c26328d..206d39cc9b3b198f42f82729eb5b1aca18062876 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -68,11 +68,15 @@
   .oneline {
     line-height: 42px;
   }
+
+  > p:last-child {
+    margin-bottom: 0;
+  }
 }
 
 .cover-block {
   text-align: center;
-  background: #f7f8fa;
+  background: $background-color;
   margin: -$gl-padding;
   margin-bottom: 0;
   padding: 44px $gl-padding;
@@ -112,5 +116,14 @@
     position: absolute;
     top: 10px;
     right: 10px;
+
+    &.left {
+      left: 10px;
+      right: auto;
+    }
   }
 }
+
+.block-connector {
+  margin-top: -1px;
+}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index fe56266284b9d5c5709ce5d2cdd0f2d00eb6226e..97a94638847576c5b8695581e3dd83f21c781865 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -1,10 +1,9 @@
 @mixin btn-default {
-  @include border-radius(2px);
+  @include border-radius(3px);
   border-width: 1px;
   border-style: solid;
-  text-transform: uppercase;
-  font-size: 13px;
-  font-weight: 600;
+  font-size: 15px;
+  font-weight: 500;
   line-height: 18px;
   padding: 11px $gl-padding;
   letter-spacing: .4px;
@@ -18,7 +17,7 @@
 
 @mixin btn-middle {
   @include btn-default;
-  @include border-radius(2px);
+  @include border-radius(3px);
   padding: 11px 24px;
 }
 
@@ -51,6 +50,10 @@
   @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #FFFFFF);
 }
 
+@mixin btn-blue-medium {
+  @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, #FFFFFF);
+}
+
 @mixin btn-orange {
   @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #FFFFFF);
 }
@@ -60,7 +63,7 @@
 }
 
 @mixin btn-gray {
-  @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, #313236);
+  @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, #313236);
 }
 
 @mixin btn-white {
@@ -75,6 +78,10 @@
     padding: 5px 10px;
   }
 
+  &.btn-nr {
+    padding: 7px 10px;
+  }
+
   &.btn-xs {
     padding: 1px 5px;
   }
@@ -91,11 +98,15 @@
     @include btn-gray;
   }
 
-  &.btn-primary,
+  &.btn-primary {
+    @include btn-blue-medium;
+  }
+
   &.btn-info {
     @include btn-blue;
   }
 
+  &.btn-close,
   &.btn-warning {
     @include btn-orange;
   }
@@ -110,20 +121,8 @@
     float: right;
   }
 
-  &.btn-close {
-    color: $gl-danger;
-    border-color: $gl-danger;
-    &:hover {
-      color: #B94A48;
-    }
-  }
-
   &.btn-reopen {
-    color: $gl-success;
-    border-color: $gl-success;
-    &:hover {
-      color: #468847;
-    }
+    /* should be same as parent class for now */
   }
 
   &.btn-grouped {
diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss
index f3ce4e3c2194787cb02a06785283718a66396ea1..20a9bfb9816b3222d64c5d21aaeb99c008698ec7 100644
--- a/app/assets/stylesheets/framework/callout.scss
+++ b/app/assets/stylesheets/framework/callout.scss
@@ -7,8 +7,8 @@
 
 /* Common styles for all types */
 .bs-callout {
-  margin: 20px 0;
-  padding: 20px;
+  margin: $gl-padding 0;
+  padding: $gl-padding;
   border-left: 3px solid $border-color;
   color: $text-color;
   background: $background-color;
@@ -42,4 +42,3 @@
   border-color: #5cA64d;
   color: #3c763d;
 }
-
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 61689aff57ecfe74115b674a0200a4517cbac345..11730000f85e32f06b75fe0c282f7c3e0e00d802 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -7,7 +7,7 @@
 
 /** COMMON CLASSES **/
 .prepend-top-10 { margin-top:10px }
-.prepend-top-default { margin-top: $gl-padding; }
+.prepend-top-default { margin-top: $gl-padding !important; }
 .prepend-top-20 { margin-top:20px }
 .prepend-left-10 { margin-left:10px }
 .prepend-left-20 { margin-left:20px }
@@ -52,6 +52,10 @@ pre {
   }
 }
 
+hr {
+  margin: $gl-padding 0;
+}
+
 .dropdown-menu > li > a {
   text-shadow: none;
 }
@@ -329,7 +333,7 @@ table {
 }
 
 .well {
-  margin-bottom: 0;
+  margin-bottom: $gl-padding;
 }
 
 .search_box {
@@ -337,10 +341,6 @@ table {
   text-align: center;
 }
 
-.task-status {
-  margin-left: 10px;
-}
-
 #nprogress .spinner {
   top: 15px !important;
   right: 10px !important;
@@ -374,14 +374,13 @@ table {
   }
 }
 
-.center-top-menu {
+.center-top-menu, .left-top-menu {
   @include nav-menu;
   text-align: center;
   margin-top: 5px;
   margin-bottom: $gl-padding;
-  height: 56px;
+  height: auto;
   margin-top: -$gl-padding;
-  padding-top: $gl-padding;
 
   &.no-bottom {
     margin-bottom: 0;
@@ -390,6 +389,28 @@ table {
   &.no-top {
     margin-top: 0;
   }
+
+  li a {
+    display: inline-block;
+    padding-top: $gl-padding;
+    padding-bottom: 11px;
+    margin-bottom: -1px;
+  }
+
+  &.bottom-border {
+    border-bottom: 1px solid $border-color;
+    height: 57px;
+  }
+
+  &.wide {
+    margin-left: -$gl-padding;
+    margin-right: -$gl-padding;
+  }
+}
+
+.left-top-menu {
+  text-align: left;
+  border-bottom: 1px solid #EEE;
 }
 
 .center-middle-menu {
@@ -433,3 +454,26 @@ table {
 .space-right {
   margin-right: 10px;
 }
+
+.alert, .progress {
+  margin-bottom: $gl-padding;
+}
+
+.new-project-item-select-holder {
+  display: inline-block;
+  position: relative;
+
+  .new-project-item-select {
+    position: absolute;
+    top: 0;
+    right: 0;
+    width: 250px !important;
+    visibility: hidden;
+  }
+}
+
+.content-separator {
+  margin-left: -$gl-padding;
+  margin-right: -$gl-padding;
+  border-top: 1px solid $border-color;
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 35db00281e5d26b055c65aac9432fd12ce68b996..cbfd4bc29b636fdde456116b7f970c0aea75e7d2 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -8,7 +8,6 @@
   border: none;
   border-top: 1px solid #E7E9EE;
   border-bottom: 1px solid #E7E9EE;
-  margin-bottom: 1em;
 
   &.readme-holder {
     border-bottom: 0;
@@ -22,10 +21,9 @@
     position: relative;
     background: $background-color;
     border-bottom: 1px solid $border-color;
-    text-shadow: 0 1px 1px #fff;
     margin: 0;
     text-align: left;
-    padding: 10px 15px;
+    padding: 10px $gl-padding;
 
     .file-actions {
       float: right;
@@ -171,4 +169,3 @@
     }
   }
 }
-
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 0edfe24f19584c8d340f4e79caa861b06901f726..032d343df44e3060ec5fc66499904856839b841d 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -22,9 +22,10 @@ input[type='text'].danger {
 }
 
 .form-actions {
-  padding: 17px 20px 18px;
-  margin-top: 18px;
-  margin-bottom: 18px;
+  margin: -$gl-padding;
+  margin-top: 0;
+  margin-bottom: -$gl-padding;
+  padding: $gl-padding;
   background-color: $background-color;
   border-top: 1px solid $border-color;
 }
@@ -73,6 +74,8 @@ label {
 
 .form-control {
   @include box-shadow(none);
+  height: 42px;
+  padding: 8px $gl-padding;
 }
 
 .wiki-content {
@@ -88,7 +91,19 @@ label {
 }
 
 .input-group {
+  .select2-container {
+    display: table-cell;
+    width: 200px !important;
+  }
   .input-group-addon {
     background-color: #f7f8fa;
   }
+  .input-group-addon:not(:first-child):not(:last-child) {
+    border-left: 0;
+    border-right: 0;
+  }
+}
+
+.help-block {
+  margin-bottom: 0;
 }
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 02ea91602e8ee3f360fd8fd2ea04968b417a716f..4dbbb56104bdca5b3dd1d411f449b9ecbc4cca8d 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -6,15 +6,17 @@ header {
   transition-duration: .3s;
 
   &.navbar-empty {
+    height: 58px;
     background: #FFF;
     border-bottom: 1px solid #EEE;
 
     .center-logo {
-      margin: 8px 0;
+      margin: 11px 0;
       text-align: center;
 
-      img {
-        height: 32px;
+      #tanuki-logo, img {
+        width: 36px;
+        height: 36px;
       }
     }
   }
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 93377e45e70a52aec9571670bb99b1233e9f978f..e93dbab0c423673007ae683e417780c04031c27f 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -4,31 +4,32 @@
  *
  */
 
-.issue-box {
-  @include border-radius(2px);
+.status-box {
+  @include border-radius(3px);
 
-  display: inline-block;
-  padding: 10px $gl-padding;
+  display: block;
+  float: left;
+  padding: 0 $gl-padding;
   font-weight: normal;
   margin-right: 10px;
   font-size: $gl-font-size;
 
-  &.issue-box-closed {
+  &.status-box-closed {
     background-color: $gl-danger;
     color: #FFF;
   }
 
-  &.issue-box-merged {
+  &.status-box-merged {
     background-color: $gl-primary;
     color: #FFF;
   }
 
-  &.issue-box-open {
-    background-color: #019875;
+  &.status-box-open {
+    background-color: $green-light;
     color: #FFF;
   }
 
-  &.issue-box-expired {
+  &.status-box-expired {
     background: #cea61b;
     color: #FFF;
   }
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index b91c15d8910f0b2fe2da7dca14863d9414685434..a1a9990241d6964d96bda9f13e882c2c7ae9c66e 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -2,9 +2,13 @@ html {
   overflow-y: scroll;
 
   &.touch .tooltip { display: none !important; }
+}
+
+body {
+  background-color: #F3F3F3 !important;
 
-  body {
-    padding-top: $header-height;
+  &.navless {
+    background-color: white !important;
   }
 }
 
@@ -18,7 +22,8 @@ html {
 }
 
 .navless-container {
-  margin-top: 30px;
+  margin-top: $header-height;
+  padding-top: $gl-padding * 2;
 }
 
 .container-limited {
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 45f3b5849bfc47cfb05d60790054838c58c232e5..1c74e525a608348f04250da3177c9fb01ce15838 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -7,7 +7,7 @@
   padding: 0;
   list-style: none;
 
-  li {
+  > li {
     padding: 10px 15px;
     min-height: 20px;
     border-bottom: 1px solid #eee;
@@ -72,13 +72,6 @@
   }
 }
 
-ol, ul {
-  &.styled {
-    li {
-      padding: 2px;
-    }
-  }
-}
 
 /** light list with border-bottom between li **/
 ul.bordered-list {
@@ -95,8 +88,14 @@ ul.bordered-list {
   }
 }
 
-li.task-list-item {
-  list-style-type: none;
+ul.task-list {
+  li.task-list-item {
+    list-style-type: none;
+  }
+
+  ul:not(.task-list) {
+    padding-left: 1.3em;
+  }
 }
 
 ul.content-list {
@@ -127,3 +126,36 @@ ul.content-list {
   }
 }
 
+.panel > .content-list {
+  li {
+    margin: 0;
+  }
+}
+
+ul.controls {
+  padding-top: 1px;
+  float: right;
+  list-style: none;
+
+  .btn {
+    padding: 10px 14px;
+  }
+
+  > li {
+    float: left;
+    margin-right: 10px;
+    
+    &:last-child {
+      margin-right: 0;
+    }
+
+    .author_link {
+      display: inline-block;
+
+      .avatar-inline {
+        margin-left: 0;
+        margin-right: 0;
+      }
+    }
+  }
+}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index cc660529cb4d00e6a655bf76cd17f55b505b76e7..4a00a197d9a0c817eaa19345e81fe6b176234e46 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -73,11 +73,8 @@
 }
 
 .referenced-users {
-  padding: 10px 0;
-  color: #999;
-  margin-left: 10px;
-  margin-top: 1px;
-  margin-right: 130px;
+  color: #4c4e54;
+  padding-top: 10px;
 }
 
 .md-preview-holder {
@@ -90,7 +87,7 @@
 
 .new_note,
 .edit_note,
-.issuable-description,
+.detail-page-description,
 .milestone-description,
 .wiki-content,
 .merge-request-form {
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 11c48d26ab53a27e50564b4c261ce0224653e6b0..41fd890f14f8046b858b3e6f56fffb4dd16ba338 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -123,7 +123,6 @@
   padding: 0;
   margin: 0;
   list-style: none;
-  margin-top: 5px;
   height: 56px;
 
   li {
@@ -131,9 +130,9 @@
 
     a {
       padding: 14px;
-      font-size: 17px;
+      font-size: 15px;
       line-height: 28px;
-      color: #7f8fa4;
+      color: #959494;
       border-bottom: 2px solid transparent;
 
       &:hover, &:active, &:focus {
@@ -143,8 +142,8 @@
     }
 
     &.active a {
-      color: #4c4e54;
-      border-bottom: 2px solid #1cacfc;
+      color: #616060;
+      border-bottom: 2px solid #4688f1;
     }
 
     .badge {
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index cea47fba19226c8ba688f5f9882c44a7b7a29549..c00709fb6bb9a3bb1d7f07080ed77ff2502610a1 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -81,10 +81,7 @@
     display: none;
   }
 
-  .center-top-menu {
-    height: 45px;
-    margin-bottom: 30px;
-
+  .center-top-menu, .left-top-menu {
     li a {
       font-size: 14px;
       padding: 19px 10px;
diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss
index 6677f94dafdfe4d2faa2c5c1223816245db1289e..2cd30491bf5de471ee1c144a0f693bb98d7840e6 100644
--- a/app/assets/stylesheets/framework/pagination.scss
+++ b/app/assets/stylesheets/framework/pagination.scss
@@ -32,3 +32,7 @@
     }
   }
 }
+
+.panel > .gl-pagination {
+  margin: 0;
+}
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
new file mode 100644
index 0000000000000000000000000000000000000000..57b9451b264d95f6a5a66c52e51ee35d55930f84
--- /dev/null
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -0,0 +1,20 @@
+.panel {
+  margin-bottom: $gl-padding;
+
+  .panel-heading {
+    padding: 7px $gl-padding;
+  }
+
+  .panel-body {
+    padding: $gl-padding;
+
+    .form-actions {
+      margin: -$gl-padding;
+      margin-top: $gl-padding;
+    }
+  }
+}
+
+.container-blank .panel .panel-heading {
+  line-height: 42px !important;  
+}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 78fff58d2328dcf57e6d6f4fb462675e05803323..af145191bc8aa4f6b9c940e60f6305e035e9e0ef 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -15,6 +15,16 @@
       border-left: none;
       padding-top: 5px;
     }
+
+    .select2-chosen {
+      color: $gl-text-color;
+    }
+
+    &.select2-default {
+      .select2-chosen {
+        color: #999;
+      }
+    }
   }
 }
 
@@ -23,6 +33,7 @@
   border: 1px solid #e7e9ed;
 }
 
+
 .select2-drop {
   @include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
   @include border-radius (0px);
@@ -48,17 +59,38 @@
   color: #313236;
 }
 
+.select2-container-multi {
+  .select2-choices {
+    @include border-radius(2px);
+    border-color: $input-border;
+    background: white;
+    padding-left: $gl-padding / 2;
+
+    .select2-search-field input {
+      padding: $gl-padding / 2;
+      font-size: 13px;
+      height: auto;
+      font-family: inherit;
+      font-size: inherit;
+    }
 
-.select2-container-multi .select2-choices {
-  @include border-radius(2px);
-  border-color: #CCC;
-}
-
-.select2-container-multi .select2-choices .select2-search-field input {
-  padding: 8px 14px;
-  font-size: 13px;
-  line-height: 18px;
-  height: auto;
+    .select2-search-choice {
+      margin: 8px 0 0 8px;
+      background: white;
+      box-shadow: none;
+      border-color: $input-border;
+      color: $gl-text-color;
+      line-height: 15px;
+
+      .select2-search-choice-close {
+        top: 5px;
+      }
+
+      &.select2-search-choice-focus {
+        border-color: $gl-text-color;
+      }
+    }
+  }
 }
 
 .select2-drop-active {
@@ -123,10 +155,16 @@
 }
 
 .user-result {
+  min-height: 24px;
+
   .user-image {
     float: left;
   }
-  .user-name {
+
+  &.no-username {
+    .user-name {
+      line-height: 24px;
+    }
   }
 }
 
@@ -143,4 +181,4 @@
 
 .ajax-users-dropdown {
   min-width: 250px !important;
-}
\ No newline at end of file
+}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index c1b0129c866038faacc0f7cd7e315d90b63d339a..458af76cb75a5f463208a23fc9a3f43e327f56cc 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -1,4 +1,7 @@
 .page-with-sidebar {
+  padding-top: $header-height;
+  transition-duration: .3s;
+
   .sidebar-wrapper {
     position: fixed;
     top: 0;
@@ -14,19 +17,15 @@
 .sidebar-wrapper {
   z-index: 99;
   background: $background-color;
-  transition-duration: .3s;
 }
 
 .content-wrapper {
-  min-height: 100vh;
   width: 100%;
   padding: 20px;
-  background: #EAEBEC;
 
   .container-fluid {
     background: #FFF;
     padding: $gl-padding;
-    min-height: 90vh;
 
     &.container-blank {
       background: none;
@@ -36,6 +35,83 @@
   }
 }
 
+.sidebar-wrapper {
+  .header-logo {
+    border-bottom: 1px solid transparent;
+    float: left;
+    height: $header-height;
+    width: $sidebar_width;
+    position: fixed;
+    z-index: 999;
+    overflow: hidden;
+    transition-duration: .3s;
+
+    a {
+      float: left;
+      height: $header-height;
+      width: 100%;
+      padding: 11px 0 11px 22px;
+      overflow: hidden;
+      outline: none;
+      transition-duration: .3s;
+
+      img {
+        width: 36px;
+        height: 36px;
+      }
+
+      #tanuki-logo, img {
+        float: left;
+      }
+
+      .gitlab-text-container {
+        width: 230px;
+
+        h3 {
+          width: 158px;
+          float: left;
+          margin: 0;
+          margin-left: 14px;
+          font-size: 19px;
+          line-height: 41px;
+          font-weight: normal;
+        }
+      }
+    }
+
+    &:hover {
+      background-color: #EEE;
+    }
+  }
+
+  .sidebar-user {
+    padding: 9px 22px;
+    position: fixed;
+    bottom: 40px;
+    width: $sidebar_width;
+    overflow: hidden;
+    transition-duration: .3s;
+
+    .username {
+      margin-left: 10px;
+      width: $sidebar_width - 2 * 10px;
+      font-size: 16px;
+      line-height: 34px;
+    }
+  }
+}
+
+
+.tanuki-shape {
+  transition: all 0.8s;
+
+  &:hover {
+    fill: rgb(255, 255, 255);
+    transition: all 0.1s;
+  }
+}
+
+
 .nav-sidebar {
   margin-top: 14 + $header-height;
   margin-bottom: 100px;
@@ -62,7 +138,7 @@
       color: $gray;
       display: block;
       text-decoration: none;
-      padding-left: 22px;
+      padding-left: 23px;
       font-weight: normal;
       outline: none;
 
@@ -86,6 +162,10 @@
         padding: 0px 8px;
         @include border-radius(6px);
       }
+
+      &.back-link i {
+        transition-duration: .3s;
+      }
     }
   }
 }
@@ -101,7 +181,6 @@
 
 @mixin expanded-sidebar {
   padding-left: $sidebar_width;
-  transition-duration: .3s;
 
   .sidebar-wrapper {
     width: $sidebar_width;
@@ -115,16 +194,15 @@
 
       &.back-link {
         i {
-          visibility: hidden;
+          opacity: 0;
         }
       }
     }
   }
 }
 
-@mixin folded-sidebar {
-  padding-left: 60px;
-  transition-duration: .3s;
+@mixin collapsed-sidebar {
+  padding-left: $sidebar_collapsed_width;
 
   .sidebar-wrapper {
     width: $sidebar_collapsed_width;
@@ -133,7 +211,7 @@
       width: $sidebar_collapsed_width;
 
       a {
-        padding-left: 12px;
+        padding-left: ($sidebar_collapsed_width - 36) / 2;
 
         .gitlab-text-container {
           display: none;
@@ -144,9 +222,13 @@
     .nav-sidebar {
       width: $sidebar_collapsed_width;
 
-      li a {
-        span {
-          display: none;
+      li {
+        width: auto;
+
+        a {
+          span {
+            display: none;
+          }
         }
       }
     }
@@ -156,7 +238,7 @@
     }
 
     .sidebar-user {
-      padding-left: 12px;
+      padding-left: ($sidebar_collapsed_width - 36) / 2;
       width: $sidebar_collapsed_width;
 
       .username {
@@ -187,11 +269,11 @@
 
 @media (max-width: $screen-md-max) {
   .page-sidebar-collapsed {
-    @include folded-sidebar;
+    @include collapsed-sidebar;
   }
 
   .page-sidebar-expanded {
-    @include folded-sidebar;
+    @include collapsed-sidebar;
   }
 
   .collapse-nav {
@@ -201,83 +283,10 @@
 
 @media(min-width: $screen-md-max) {
   .page-sidebar-collapsed {
-    @include folded-sidebar;
+    @include collapsed-sidebar;
   }
 
   .page-sidebar-expanded {
     @include expanded-sidebar;
   }
 }
-
-.sidebar-user {
-  padding: 9px 22px;
-  position: fixed;
-  bottom: 40px;
-  width: $sidebar_width;
-  overflow: hidden;
-  transition-duration: .3s;
-
-  .username {
-    margin-left: 10px;
-    width: $sidebar_width - 2 * 10px;
-    font-size: 16px;
-    line-height: 34px;
-  }
-}
-
-.sidebar-wrapper {
-  .header-logo {
-    border-bottom: 1px solid transparent;
-    float: left;
-    height: $header-height;
-    width: $sidebar_width;
-    overflow: hidden;
-    transition-duration: .3s;
-
-    a {
-      float: left;
-      height: $header-height;
-      width: 100%;
-      padding: 10px 22px;
-      overflow: hidden;
-      outline: none;
-
-      img {
-        width: 36px;
-        height: 36px;
-      }
-
-      #tanuki-logo, img {
-        float: left;
-      }
-
-      .gitlab-text-container {
-        width: 230px;
-
-        h3 {
-          width: 158px;
-          float: left;
-          margin: 0;
-          margin-left: 14px;
-          font-size: 19px;
-          line-height: 41px;
-          font-weight: normal;
-        }
-      }
-    }
-
-    &:hover {
-      background-color: #EEE;
-    }
-  }
-}
-
-
-.tanuki-shape {
-  transition: all 0.8s;
-
-  &:hover {
-    fill: rgb(255, 255, 255);
-    transition: all 0.1s;
-  }
-}
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index 66e16e8df75764e9838c46ee4e63d7d5e9df8e7f..793ab3d9bb9bf50e50a3953864233d5ea4f58df5 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -6,6 +6,8 @@
 
 table {
   &.table {
+    margin-bottom: $gl-padding;
+    
     .dropdown-menu a {
       text-decoration: none;
     }
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index eb53c4153d3e794e48443da57f213ae6315538cd..ff41e26ed8adc4185f783f34a567820392d27e44 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -10,8 +10,7 @@
     margin-left: -$gl-padding;
     margin-right: -$gl-padding;
     color: $gl-gray;
-    border-bottom: 1px solid #ECEEF1;
-    border-right: 1px solid #ECEEF1;
+    border-bottom: 1px solid $border-white-light;
 
     &:target {
       background: $hover;
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 50c0cf61f4e0dfe6f2efdd10ef092ac9a6f91b30..94f0ed761dfbd25cb8307e8ac909de54c3c601b3 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -190,6 +190,10 @@
     .btn {
       min-width: 124px;
     }
+
+    .btn-clipboard {
+      min-width: 0px;
+    }
   }
 
   &.panel-small {
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index ba0312ba0db5692549945a4f56dc7d928129e0ae..c3e4ad0ad0039317a6a099ed988936f783473e9f 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -181,6 +181,10 @@ body {
   line-height: 1.3;
   font-size: 1.25em;
   font-weight: 600;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
 }
 
 .page-title-empty {
@@ -216,6 +220,7 @@ pre {
 
 .monospace {
   font-family: $monospace_font;
+  font-size: 90%;
 }
 
 code {
@@ -256,3 +261,9 @@ textarea.js-gfm-input {
 .strikethrough {
   text-decoration: line-through;
 }
+
+h1, h2, h3, h4 {
+  small {
+    color: $gl-gray;
+  }
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 91954683c3e8db28989abfd7fd970c2e35cd3a73..af75123b0af367508648ead871ee196e231dc049 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -1,9 +1,9 @@
-$hover: #FFFAF1;
+$hover: #faf9f9;
 $gl-text-color: #54565B;
 $gl-text-green: #4A2;
 $gl-text-red: #D12F19;
 $gl-text-orange: #D90;
-$gl-header-color: #4c4e54;
+$gl-header-color: #323232;
 $gl-link-color: #333c48;
 $md-text-color: #444;
 $md-link-color: #3084bb;
@@ -15,13 +15,14 @@ $sidebar_width: 230px;
 $avatar_radius: 50%;
 $code_font_size: 13px;
 $code_line_height: 1.5;
-$border-color: #dce0e6;
+$border-color: #efeff1;
 $table-border-color: #eef0f2;
-$background-color: #F7F8FA;
+$background-color: #faf9f9;
 $header-height: 58px;
-$fixed-layout-width: 1200px;
-$gl-gray: #7f8fa4;
+$fixed-layout-width: 1280px;
+$gl-gray: #5a5a5a;
 $gl-padding: 16px;
+$gl-padding-top:10px;
 $gl-avatar-size: 46px;
 
 /*
@@ -29,12 +30,12 @@ $gl-avatar-size: 46px;
  */
 
 $white-light: #FFFFFF;
-$white-normal: #DCE0E5;
-$white-dark: #E4E7ED;
+$white-normal: #ededed;
+$white-dark: #ededed;
 
-$gray-light: #F0F2F5;
-$gray-normal: #DCE0E5;
-$gray-dark: #E4E7ED;
+$gray-light: #f7f7f7;
+$gray-normal: #ededed;
+$gray-dark: #ededed;
 
 $green-light: #31AF64;
 $green-normal: #2FAA60;
@@ -44,6 +45,10 @@ $blue-light: #2EA8E5;
 $blue-normal: #2D9FD8;
 $blue-dark: #2897CE;
 
+$blue-medium-light: #3498CB;
+$blue-medium: #2F8EBF;
+$blue-medium-dark: #2D86B4;
+
 $orange-light: #FC6443;
 $orange-normal: #E75E40;
 $orange-dark: #CE5237;
@@ -52,11 +57,11 @@ $red-light: #F43263;
 $red-normal: #E52C5A;
 $red-dark: #D22852;
 
-$border-white-light: #E3E7EC;
+$border-white-light: #F1F2F4;
 $border-white-normal: #D6DAE2;
 $border-white-dark: #C6CACF;
 
-$border-gray-light: #DCE0E5;
+$border-gray-light: #d1d1d1;
 $border-gray-normal: #D6DAE2;
 $border-gray-dark: #C6CACF;
 
@@ -76,6 +81,8 @@ $border-red-light: #E52C5A;
 $border-red-normal: #D22852;
 $border-red-dark: #CA264F;
 
+/* header */
+$light-grey-header: #faf9f9;
 
 /*
  * State colors:
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
new file mode 100644
index 0000000000000000000000000000000000000000..87dd30f41114daf3ab6d6bad96e781b38aadcaab
--- /dev/null
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -0,0 +1,125 @@
+.awards {
+  @include clearfix;
+  line-height: 34px;
+
+  .emoji-icon {
+    width: 20px;
+    height: 20px;
+    margin: 7px 0 0 5px;
+  }
+
+  .award {
+    @include border-radius(5px);
+
+    border: 1px solid;
+    padding: 0px 10px;
+    float: left;
+    margin-right: 5px;
+    border-color: $border-color;
+    cursor: pointer;
+
+    &:hover {
+      background-color: #dce0e5;
+    }
+
+    &.active {
+      border-color: $border-gray-light;
+      background-color: $gray-light;
+
+      &:hover {
+        background-color: #dce0e5;
+      }
+
+      .counter {
+        font-weight: bold;
+      }
+    }
+
+    .icon {
+      float: left;
+      margin-right: 10px;
+    }
+
+    .counter {
+      float: left;
+    }
+  }
+
+  .awards-controls {
+    position: relative;
+    margin-left: 10px;
+    float: left;
+
+    .add-award {
+      font-size: 24px;
+      color: $gl-gray;
+      position: relative;
+      top: 2px;
+
+      &:hover,
+      &:link {
+        text-decoration: none;
+      }
+    }
+
+    .emoji-menu{
+      position: absolute;
+      top: 100%;
+      left: 0;
+      z-index: 1000;
+      display: none;
+      float: left;
+      min-width: 160px;
+      padding: 5px 0;
+      margin: 2px 0 0;
+      font-size: 14px;
+      text-align: left;
+      list-style: none;
+      background-color: #fff;
+      -webkit-background-clip: padding-box;
+      background-clip: padding-box;
+      border: 1px solid #ccc;
+      border: 1px solid rgba(0,0,0,.15);
+      border-radius: 4px;
+      -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
+      box-shadow: 0 6px 12px rgba(0,0,0,.175);
+
+      .emoji-menu-content {
+        padding: $gl-padding;
+        width: 300px;
+        height: 300px;
+        overflow-y: scroll;
+
+        h5 {
+          clear: left;
+        }
+
+        ul {
+          list-style-type: none;
+          margin-left: -20px;
+          margin-bottom: 20px;
+          overflow: auto;
+        }
+
+        input.emoji-search{
+          background: image-url("icon-search.png") 240px no-repeat;
+        }
+
+        li {
+          cursor: pointer;
+          width: 30px;
+          height: 30px;
+          text-align: center;
+          float: left;
+          margin: 3px;
+          list-decorate: none;
+          @include border-radius(5px);
+
+          &:hover {
+            background-color: #ccc;
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index da9965f007af1e409d432381dc49b728cdde31bb..3c2997c1d5adc11330a4db8794abbdea1736e962 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -67,9 +67,4 @@
       color: #3084bb !important;
     }
   }
-
-  .build-top-menu {
-    margin-top: 0;
-    margin-bottom: 2px;
-  }
 }
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index a0e5f7554edecb2f5c1a1cffdab6e87adf36de01..17245d3be7ba4d00a96694e81d2df244e2605303 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -2,10 +2,6 @@
   display: block;
 }
 
-.commit-title{
-  margin-bottom: 10px;
-}
-
 .commit-author, .commit-committer{
   display: block;
   color: #999;
@@ -41,6 +37,8 @@
 .commit-box {
   .commit-title {
     margin: 0;
+    font-size: 23px;
+    color: #313236;
   }
 
   .commit-description {
@@ -108,16 +106,3 @@
     z-index: 2;
   }
 }
-
-.commit-ci-menu {
-  padding: 0;
-  margin: 0;
-  list-style: none;
-  margin-top: 5px;
-  height: 56px;
-  margin: -16px;
-  padding: 16px;
-  text-align: center;
-  margin-top: 0px;
-  margin-bottom: 2px;
-}
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
new file mode 100644
index 0000000000000000000000000000000000000000..deab805dbc2988ab003f6f5b47675e7cb1697a92
--- /dev/null
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -0,0 +1,33 @@
+.detail-page-header {
+  margin: -$gl-padding;
+  padding: 7px $gl-padding;
+  margin-bottom: 0px;
+  border-bottom: 1px solid $border-color;
+  color: #5c5d5e;
+  font-size: 16px;
+  line-height: 34px;
+
+  .author {
+    color: #5c5d5e;
+  }
+
+  .identifier {
+    color: #5c5d5e;
+  }
+}
+
+.detail-page-description {
+  .title {
+    margin: 0;
+    font-size: 23px;
+    color: #313236;
+  }
+
+  .description {
+    margin-top: 6px;
+
+    p:last-child {
+      margin-bottom: 0;
+    }
+  }
+}
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index e2c521af91ec1dbce8e1135a86f44aa06d17b7ee..39d916cd336002e58b8a7896935a2281553dc317 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -19,48 +19,38 @@
       color: #B94A48;
     }
   }
-  .commit-button-annotation {
-    display: inline-block;
-    margin: 0;
-    padding: 2px;
-
-    > * {
-      float: left;
-    }
-
-    .message {
-      display: inline-block;
-      margin: 5px 8px 0 8px;
-    }
-  }
 
   .file-title {
     @extend .monospace;
+
+    line-height: 42px;
+    padding-top: 7px;
+    padding-bottom: 7px;
   }
 
   .editor-ref {
     background: $background-color;
-    padding: 11px 15px;
+    padding-right: $gl-padding;
     border-right: 1px solid $border-color;
-    display: inline-block;
-    margin: -5px -5px;
+    display: block;
+    float: left;
     margin-right: 10px;
   }
 
   .editor-file-name {
-    .new-file-name {
-      display: inline-block;
-      width: 450px;
-    }
+    @extend .monospace;
+    
+    float: left;
+    margin-right: 10px;
+  }
 
-    .form-control {
-      margin-top: -3px;
-    }
+  .new-file-name {
+    display: inline-block;
+    width: 450px;
+    float: left;
   }
 
-  .form-actions {
-    margin: -$gl-padding;
-    margin-top: 0;
-    padding: $gl-padding
+  .select2 {
+    float: right;
   }
 }
diff --git a/app/assets/stylesheets/pages/emojis.scss b/app/assets/stylesheets/pages/emojis.scss
new file mode 100644
index 0000000000000000000000000000000000000000..89a94c5a780b5a7a552436475251d008370ada23
--- /dev/null
+++ b/app/assets/stylesheets/pages/emojis.scss
@@ -0,0 +1,1272 @@
+/*
+File is generated by https://github.com/jakesgordon/sprite-factory and midified manualy
+The source: gemojione gem.
+*/
+
+.emoji-icon{
+  background-image: image-url("emoji.png");
+  background-repeat: no-repeat;
+}
+
+.emoji-0023-20E3 { background-position: 0px 0px; }
+.emoji-0030-20E3 { background-position: -20px 0px; }
+.emoji-0031-20E3 { background-position: -40px 0px; }
+.emoji-0032-20E3 { background-position: -60px 0px; }
+.emoji-0033-20E3 { background-position: -80px 0px; }
+.emoji-0034-20E3 { background-position: -100px 0px; }
+.emoji-0035-20E3 { background-position: -120px 0px; }
+.emoji-0036-20E3 { background-position: -140px 0px; }
+.emoji-0037-20E3 { background-position: -160px 0px; }
+.emoji-0038-20E3 { background-position: -180px 0px; }
+.emoji-0039-20E3 { background-position: -200px 0px; }
+.emoji-00A9 { background-position: -220px 0px; }
+.emoji-00AE { background-position: -240px 0px; }
+.emoji-1F004 { background-position: -260px 0px; }
+.emoji-1F0CF { background-position: -280px 0px; }
+.emoji-1F170 { background-position: -300px 0px; }
+.emoji-1F171 { background-position: -320px 0px; }
+.emoji-1F17E { background-position: -340px 0px; }
+.emoji-1F17F { background-position: -360px 0px; }
+.emoji-1F18E { background-position: -380px 0px; }
+.emoji-1F191 { background-position: -400px 0px; }
+.emoji-1F192 { background-position: -420px 0px; }
+.emoji-1F193 { background-position: -440px 0px; }
+.emoji-1F194 { background-position: -460px 0px; }
+.emoji-1F195 { background-position: -480px 0px; }
+.emoji-1F196 { background-position: -500px 0px; }
+.emoji-1F197 { background-position: -520px 0px; }
+.emoji-1F198 { background-position: -540px 0px; }
+.emoji-1F199 { background-position: -560px 0px; }
+.emoji-1F19A { background-position: -580px 0px; }
+.emoji-1F1E6-1F1E8 { background-position: -600px 0px; }
+.emoji-1F1E6-1F1E9 { background-position: -620px 0px; }
+.emoji-1F1E6-1F1EA { background-position: -640px 0px; }
+.emoji-1F1E6-1F1EB { background-position: -660px 0px; }
+.emoji-1F1E6-1F1EC { background-position: -680px 0px; }
+.emoji-1F1E6-1F1EE { background-position: -700px 0px; }
+.emoji-1F1E6-1F1F1 { background-position: -720px 0px; }
+.emoji-1F1E6-1F1F2 { background-position: -740px 0px; }
+.emoji-1F1E6-1F1F4 { background-position: -760px 0px; }
+.emoji-1F1E6-1F1F7 { background-position: -780px 0px; }
+.emoji-1F1E6-1F1F9 { background-position: -800px 0px; }
+.emoji-1F1E6-1F1FA { background-position: -820px 0px; }
+.emoji-1F1E6-1F1FC { background-position: -840px 0px; }
+.emoji-1F1E6-1F1FF { background-position: -860px 0px; }
+.emoji-1F1E7-1F1E6 { background-position: -880px 0px; }
+.emoji-1F1E7-1F1E7 { background-position: -900px 0px; }
+.emoji-1F1E7-1F1E9 { background-position: -920px 0px; }
+.emoji-1F1E7-1F1EA { background-position: -940px 0px; }
+.emoji-1F1E7-1F1EB { background-position: -960px 0px; }
+.emoji-1F1E7-1F1EC { background-position: -980px 0px; }
+.emoji-1F1E7-1F1ED { background-position: -1000px 0px; }
+.emoji-1F1E7-1F1EE { background-position: -1020px 0px; }
+.emoji-1F1E7-1F1EF { background-position: -1040px 0px; }
+.emoji-1F1E7-1F1F2 { background-position: -1060px 0px; }
+.emoji-1F1E7-1F1F3 { background-position: -1080px 0px; }
+.emoji-1F1E7-1F1F4 { background-position: -1100px 0px; }
+.emoji-1F1E7-1F1F7 { background-position: -1120px 0px; }
+.emoji-1F1E7-1F1F8 { background-position: -1140px 0px; }
+.emoji-1F1E7-1F1F9 { background-position: -1160px 0px; }
+.emoji-1F1E7-1F1FC { background-position: -1180px 0px; }
+.emoji-1F1E7-1F1FE { background-position: -1200px 0px; }
+.emoji-1F1E7-1F1FF { background-position: -1220px 0px; }
+.emoji-1F1E8-1F1E6 { background-position: -1240px 0px; }
+.emoji-1F1E8-1F1E9 { background-position: -1260px 0px; }
+.emoji-1F1E8-1F1EB { background-position: -1280px 0px; }
+.emoji-1F1E8-1F1EC { background-position: -1300px 0px; }
+.emoji-1F1E8-1F1ED { background-position: -1320px 0px; }
+.emoji-1F1E8-1F1EE { background-position: -1340px 0px; }
+.emoji-1F1E8-1F1F1 { background-position: -1360px 0px; }
+.emoji-1F1E8-1F1F2 { background-position: -1380px 0px; }
+.emoji-1F1E8-1F1F3 { background-position: -1400px 0px; }
+.emoji-1F1E8-1F1F4 { background-position: -1420px 0px; }
+.emoji-1F1E8-1F1F7 { background-position: -1440px 0px; }
+.emoji-1F1E8-1F1FA { background-position: -1460px 0px; }
+.emoji-1F1E8-1F1FB { background-position: -1480px 0px; }
+.emoji-1F1E8-1F1FE { background-position: -1500px 0px; }
+.emoji-1F1E8-1F1FF { background-position: -1520px 0px; }
+.emoji-1F1E9-1F1EA { background-position: -1540px 0px; }
+.emoji-1F1E9-1F1EF { background-position: -1560px 0px; }
+.emoji-1F1E9-1F1F0 { background-position: -1580px 0px; }
+.emoji-1F1E9-1F1F2 { background-position: -1600px 0px; }
+.emoji-1F1E9-1F1F4 { background-position: -1620px 0px; }
+.emoji-1F1E9-1F1FF { background-position: -1640px 0px; }
+.emoji-1F1EA-1F1E8 { background-position: -1660px 0px; }
+.emoji-1F1EA-1F1EA { background-position: -1680px 0px; }
+.emoji-1F1EA-1F1EC { background-position: -1700px 0px; }
+.emoji-1F1EA-1F1ED { background-position: -1720px 0px; }
+.emoji-1F1EA-1F1F7 { background-position: -1740px 0px; }
+.emoji-1F1EA-1F1F8 { background-position: -1760px 0px; }
+.emoji-1F1EA-1F1F9 { background-position: -1780px 0px; }
+.emoji-1F1EB-1F1EE { background-position: -1800px 0px; }
+.emoji-1F1EB-1F1EF { background-position: -1820px 0px; }
+.emoji-1F1EB-1F1F0 { background-position: -1840px 0px; }
+.emoji-1F1EB-1F1F2 { background-position: -1860px 0px; }
+.emoji-1F1EB-1F1F4 { background-position: -1880px 0px; }
+.emoji-1F1EB-1F1F7 { background-position: -1900px 0px; }
+.emoji-1F1EC-1F1E6 { background-position: -1920px 0px; }
+.emoji-1F1EC-1F1E7 { background-position: -1940px 0px; }
+.emoji-1F1EC-1F1E9 { background-position: -1960px 0px; }
+.emoji-1F1EC-1F1EA { background-position: -1980px 0px; }
+.emoji-1F1EC-1F1ED { background-position: -2000px 0px; }
+.emoji-1F1EC-1F1EE { background-position: -2020px 0px; }
+.emoji-1F1EC-1F1F1 { background-position: -2040px 0px; }
+.emoji-1F1EC-1F1F2 { background-position: -2060px 0px; }
+.emoji-1F1EC-1F1F3 { background-position: -2080px 0px; }
+.emoji-1F1EC-1F1F6 { background-position: -2100px 0px; }
+.emoji-1F1EC-1F1F7 { background-position: -2120px 0px; }
+.emoji-1F1EC-1F1F9 { background-position: -2140px 0px; }
+.emoji-1F1EC-1F1FA { background-position: -2160px 0px; }
+.emoji-1F1EC-1F1FC { background-position: -2180px 0px; }
+.emoji-1F1EC-1F1FE { background-position: -2200px 0px; }
+.emoji-1F1ED-1F1F0 { background-position: -2220px 0px; }
+.emoji-1F1ED-1F1F3 { background-position: -2240px 0px; }
+.emoji-1F1ED-1F1F7 { background-position: -2260px 0px; }
+.emoji-1F1ED-1F1F9 { background-position: -2280px 0px; }
+.emoji-1F1ED-1F1FA { background-position: -2300px 0px; }
+.emoji-1F1EE-1F1E9 { background-position: -2320px 0px; }
+.emoji-1F1EE-1F1EA { background-position: -2340px 0px; }
+.emoji-1F1EE-1F1F1 { background-position: -2360px 0px; }
+.emoji-1F1EE-1F1F3 { background-position: -2380px 0px; }
+.emoji-1F1EE-1F1F6 { background-position: -2400px 0px; }
+.emoji-1F1EE-1F1F7 { background-position: -2420px 0px; }
+.emoji-1F1EE-1F1F8 { background-position: -2440px 0px; }
+.emoji-1F1EE-1F1F9 { background-position: -2460px 0px; }
+.emoji-1F1EF-1F1EA { background-position: -2480px 0px; }
+.emoji-1F1EF-1F1F2 { background-position: -2500px 0px; }
+.emoji-1F1EF-1F1F4 { background-position: -2520px 0px; }
+.emoji-1F1EF-1F1F5 { background-position: -2540px 0px; }
+.emoji-1F1F0-1F1EA { background-position: -2560px 0px; }
+.emoji-1F1F0-1F1EC { background-position: -2580px 0px; }
+.emoji-1F1F0-1F1ED { background-position: -2600px 0px; }
+.emoji-1F1F0-1F1EE { background-position: -2620px 0px; }
+.emoji-1F1F0-1F1F2 { background-position: -2640px 0px; }
+.emoji-1F1F0-1F1F3 { background-position: -2660px 0px; }
+.emoji-1F1F0-1F1F5 { background-position: -2680px 0px; }
+.emoji-1F1F0-1F1F7 { background-position: -2700px 0px; }
+.emoji-1F1F0-1F1FC { background-position: -2720px 0px; }
+.emoji-1F1F0-1F1FE { background-position: -2740px 0px; }
+.emoji-1F1F0-1F1FF { background-position: -2760px 0px; }
+.emoji-1F1F1-1F1E6 { background-position: -2780px 0px; }
+.emoji-1F1F1-1F1E7 { background-position: -2800px 0px; }
+.emoji-1F1F1-1F1E8 { background-position: -2820px 0px; }
+.emoji-1F1F1-1F1EE { background-position: -2840px 0px; }
+.emoji-1F1F1-1F1F0 { background-position: -2860px 0px; }
+.emoji-1F1F1-1F1F7 { background-position: -2880px 0px; }
+.emoji-1F1F1-1F1F8 { background-position: -2900px 0px; }
+.emoji-1F1F1-1F1F9 { background-position: -2920px 0px; }
+.emoji-1F1F1-1F1FA { background-position: -2940px 0px; }
+.emoji-1F1F1-1F1FB { background-position: -2960px 0px; }
+.emoji-1F1F1-1F1FE { background-position: -2980px 0px; }
+.emoji-1F1F2-1F1E6 { background-position: -3000px 0px; }
+.emoji-1F1F2-1F1E8 { background-position: -3020px 0px; }
+.emoji-1F1F2-1F1E9 { background-position: -3040px 0px; }
+.emoji-1F1F2-1F1EA { background-position: -3060px 0px; }
+.emoji-1F1F2-1F1EC { background-position: -3080px 0px; }
+.emoji-1F1F2-1F1ED { background-position: -3100px 0px; }
+.emoji-1F1F2-1F1F0 { background-position: -3120px 0px; }
+.emoji-1F1F2-1F1F1 { background-position: -3140px 0px; }
+.emoji-1F1F2-1F1F2 { background-position: -3160px 0px; }
+.emoji-1F1F2-1F1F3 { background-position: -3180px 0px; }
+.emoji-1F1F2-1F1F4 { background-position: -3200px 0px; }
+.emoji-1F1F2-1F1F7 { background-position: -3220px 0px; }
+.emoji-1F1F2-1F1F8 { background-position: -3240px 0px; }
+.emoji-1F1F2-1F1F9 { background-position: -3260px 0px; }
+.emoji-1F1F2-1F1FA { background-position: -3280px 0px; }
+.emoji-1F1F2-1F1FB { background-position: -3300px 0px; }
+.emoji-1F1F2-1F1FC { background-position: -3320px 0px; }
+.emoji-1F1F2-1F1FD { background-position: -3340px 0px; }
+.emoji-1F1F2-1F1FE { background-position: -3360px 0px; }
+.emoji-1F1F2-1F1FF { background-position: -3380px 0px; }
+.emoji-1F1F3-1F1E6 { background-position: -3400px 0px; }
+.emoji-1F1F3-1F1E8 { background-position: -3420px 0px; }
+.emoji-1F1F3-1F1EA { background-position: -3440px 0px; }
+.emoji-1F1F3-1F1EC { background-position: -3460px 0px; }
+.emoji-1F1F3-1F1EE { background-position: -3480px 0px; }
+.emoji-1F1F3-1F1F1 { background-position: -3500px 0px; }
+.emoji-1F1F3-1F1F4 { background-position: -3520px 0px; }
+.emoji-1F1F3-1F1F5 { background-position: -3540px 0px; }
+.emoji-1F1F3-1F1F7 { background-position: -3560px 0px; }
+.emoji-1F1F3-1F1FA { background-position: -3580px 0px; }
+.emoji-1F1F3-1F1FF { background-position: -3600px 0px; }
+.emoji-1F1F4-1F1F2 { background-position: -3620px 0px; }
+.emoji-1F1F5-1F1E6 { background-position: -3640px 0px; }
+.emoji-1F1F5-1F1EA { background-position: -3660px 0px; }
+.emoji-1F1F5-1F1EB { background-position: -3680px 0px; }
+.emoji-1F1F5-1F1EC { background-position: -3700px 0px; }
+.emoji-1F1F5-1F1ED { background-position: -3720px 0px; }
+.emoji-1F1F5-1F1F0 { background-position: -3740px 0px; }
+.emoji-1F1F5-1F1F1 { background-position: -3760px 0px; }
+.emoji-1F1F5-1F1F7 { background-position: -3780px 0px; }
+.emoji-1F1F5-1F1F8 { background-position: -3800px 0px; }
+.emoji-1F1F5-1F1F9 { background-position: -3820px 0px; }
+.emoji-1F1F5-1F1FC { background-position: -3840px 0px; }
+.emoji-1F1F5-1F1FE { background-position: -3860px 0px; }
+.emoji-1F1F6-1F1E6 { background-position: -3880px 0px; }
+.emoji-1F1F7-1F1F4 { background-position: -3900px 0px; }
+.emoji-1F1F7-1F1F8 { background-position: -3920px 0px; }
+.emoji-1F1F7-1F1FA { background-position: -3940px 0px; }
+.emoji-1F1F7-1F1FC { background-position: -3960px 0px; }
+.emoji-1F1F8-1F1E6 { background-position: -3980px 0px; }
+.emoji-1F1F8-1F1E7 { background-position: -4000px 0px; }
+.emoji-1F1F8-1F1E8 { background-position: -4020px 0px; }
+.emoji-1F1F8-1F1E9 { background-position: -4040px 0px; }
+.emoji-1F1F8-1F1EA { background-position: -4060px 0px; }
+.emoji-1F1F8-1F1EC { background-position: -4080px 0px; }
+.emoji-1F1F8-1F1ED { background-position: -4100px 0px; }
+.emoji-1F1F8-1F1EE { background-position: -4120px 0px; }
+.emoji-1F1F8-1F1F0 { background-position: -4140px 0px; }
+.emoji-1F1F8-1F1F1 { background-position: -4160px 0px; }
+.emoji-1F1F8-1F1F2 { background-position: -4180px 0px; }
+.emoji-1F1F8-1F1F3 { background-position: -4200px 0px; }
+.emoji-1F1F8-1F1F4 { background-position: -4220px 0px; }
+.emoji-1F1F8-1F1F7 { background-position: -4240px 0px; }
+.emoji-1F1F8-1F1F9 { background-position: -4260px 0px; }
+.emoji-1F1F8-1F1FB { background-position: -4280px 0px; }
+.emoji-1F1F8-1F1FE { background-position: -4300px 0px; }
+.emoji-1F1F8-1F1FF { background-position: -4320px 0px; }
+.emoji-1F1F9-1F1E9 { background-position: -4340px 0px; }
+.emoji-1F1F9-1F1EC { background-position: -4360px 0px; }
+.emoji-1F1F9-1F1ED { background-position: -4380px 0px; }
+.emoji-1F1F9-1F1EF { background-position: -4400px 0px; }
+.emoji-1F1F9-1F1F1 { background-position: -4420px 0px; }
+.emoji-1F1F9-1F1F2 { background-position: -4440px 0px; }
+.emoji-1F1F9-1F1F3 { background-position: -4460px 0px; }
+.emoji-1F1F9-1F1F4 { background-position: -4480px 0px; }
+.emoji-1F1F9-1F1F7 { background-position: -4500px 0px; }
+.emoji-1F1F9-1F1F9 { background-position: -4520px 0px; }
+.emoji-1F1F9-1F1FB { background-position: -4540px 0px; }
+.emoji-1F1F9-1F1FC { background-position: -4560px 0px; }
+.emoji-1F1F9-1F1FF { background-position: -4580px 0px; }
+.emoji-1F1FA-1F1E6 { background-position: -4600px 0px; }
+.emoji-1F1FA-1F1EC { background-position: -4620px 0px; }
+.emoji-1F1FA-1F1F8 { background-position: -4640px 0px; }
+.emoji-1F1FA-1F1FE { background-position: -4660px 0px; }
+.emoji-1F1FA-1F1FF { background-position: -4680px 0px; }
+.emoji-1F1FB-1F1E6 { background-position: -4700px 0px; }
+.emoji-1F1FB-1F1E8 { background-position: -4720px 0px; }
+.emoji-1F1FB-1F1EA { background-position: -4740px 0px; }
+.emoji-1F1FB-1F1EE { background-position: -4760px 0px; }
+.emoji-1F1FB-1F1F3 { background-position: -4780px 0px; }
+.emoji-1F1FB-1F1FA { background-position: -4800px 0px; }
+.emoji-1F1FC-1F1EB { background-position: -4820px 0px; }
+.emoji-1F1FC-1F1F8 { background-position: -4840px 0px; }
+.emoji-1F1FD-1F1F0 { background-position: -4860px 0px; }
+.emoji-1F1FE-1F1EA { background-position: -4880px 0px; }
+.emoji-1F1FF-1F1E6 { background-position: -4900px 0px; }
+.emoji-1F1FF-1F1F2 { background-position: -4920px 0px; }
+.emoji-1F1FF-1F1FC { background-position: -4940px 0px; }
+.emoji-1F201 { background-position: -4960px 0px; }
+.emoji-1F202 { background-position: -4980px 0px; }
+.emoji-1F21A { background-position: -5000px 0px; }
+.emoji-1F22F { background-position: -5020px 0px; }
+.emoji-1F232 { background-position: -5040px 0px; }
+.emoji-1F233 { background-position: -5060px 0px; }
+.emoji-1F234 { background-position: -5080px 0px; }
+.emoji-1F235 { background-position: -5100px 0px; }
+.emoji-1F236 { background-position: -5120px 0px; }
+.emoji-1F237 { background-position: -5140px 0px; }
+.emoji-1F238 { background-position: -5160px 0px; }
+.emoji-1F239 { background-position: -5180px 0px; }
+.emoji-1F23A { background-position: -5200px 0px; }
+.emoji-1F250 { background-position: -5220px 0px; }
+.emoji-1F251 { background-position: -5240px 0px; }
+.emoji-1F300 { background-position: -5260px 0px; }
+.emoji-1F301 { background-position: -5280px 0px; }
+.emoji-1F302 { background-position: -5300px 0px; }
+.emoji-1F303 { background-position: -5320px 0px; }
+.emoji-1F304 { background-position: -5340px 0px; }
+.emoji-1F305 { background-position: -5360px 0px; }
+.emoji-1F306 { background-position: -5380px 0px; }
+.emoji-1F307 { background-position: -5400px 0px; }
+.emoji-1F308 { background-position: -5420px 0px; }
+.emoji-1F309 { background-position: -5440px 0px; }
+.emoji-1F30A { background-position: -5460px 0px; }
+.emoji-1F30B { background-position: -5480px 0px; }
+.emoji-1F30C { background-position: -5500px 0px; }
+.emoji-1F30D { background-position: -5520px 0px; }
+.emoji-1F30E { background-position: -5540px 0px; }
+.emoji-1F30F { background-position: -5560px 0px; }
+.emoji-1F310 { background-position: -5580px 0px; }
+.emoji-1F311 { background-position: -5600px 0px; }
+.emoji-1F312 { background-position: -5620px 0px; }
+.emoji-1F313 { background-position: -5640px 0px; }
+.emoji-1F314 { background-position: -5660px 0px; }
+.emoji-1F315 { background-position: -5680px 0px; }
+.emoji-1F316 { background-position: -5700px 0px; }
+.emoji-1F317 { background-position: -5720px 0px; }
+.emoji-1F318 { background-position: -5740px 0px; }
+.emoji-1F319 { background-position: -5760px 0px; }
+.emoji-1F31A { background-position: -5780px 0px; }
+.emoji-1F31B { background-position: -5800px 0px; }
+.emoji-1F31C { background-position: -5820px 0px; }
+.emoji-1F31D { background-position: -5840px 0px; }
+.emoji-1F31E { background-position: -5860px 0px; }
+.emoji-1F31F { background-position: -5880px 0px; }
+.emoji-1F320 { background-position: -5900px 0px; }
+.emoji-1F321 { background-position: -5920px 0px; }
+.emoji-1F327 { background-position: -5940px 0px; }
+.emoji-1F328 { background-position: -5960px 0px; }
+.emoji-1F329 { background-position: -5980px 0px; }
+.emoji-1F32A { background-position: -6000px 0px; }
+.emoji-1F32B { background-position: -6020px 0px; }
+.emoji-1F32C { background-position: -6040px 0px; }
+.emoji-1F330 { background-position: -6060px 0px; }
+.emoji-1F331 { background-position: -6080px 0px; }
+.emoji-1F332 { background-position: -6100px 0px; }
+.emoji-1F333 { background-position: -6120px 0px; }
+.emoji-1F334 { background-position: -6140px 0px; }
+.emoji-1F335 { background-position: -6160px 0px; }
+.emoji-1F336 { background-position: -6180px 0px; }
+.emoji-1F337 { background-position: -6200px 0px; }
+.emoji-1F338 { background-position: -6220px 0px; }
+.emoji-1F339 { background-position: -6240px 0px; }
+.emoji-1F33A { background-position: -6260px 0px; }
+.emoji-1F33B { background-position: -6280px 0px; }
+.emoji-1F33C { background-position: -6300px 0px; }
+.emoji-1F33D { background-position: -6320px 0px; }
+.emoji-1F33E { background-position: -6340px 0px; }
+.emoji-1F33F { background-position: -6360px 0px; }
+.emoji-1F340 { background-position: -6380px 0px; }
+.emoji-1F341 { background-position: -6400px 0px; }
+.emoji-1F342 { background-position: -6420px 0px; }
+.emoji-1F343 { background-position: -6440px 0px; }
+.emoji-1F344 { background-position: -6460px 0px; }
+.emoji-1F345 { background-position: -6480px 0px; }
+.emoji-1F346 { background-position: -6500px 0px; }
+.emoji-1F347 { background-position: -6520px 0px; }
+.emoji-1F348 { background-position: -6540px 0px; }
+.emoji-1F349 { background-position: -6560px 0px; }
+.emoji-1F34A { background-position: -6580px 0px; }
+.emoji-1F34B { background-position: -6600px 0px; }
+.emoji-1F34C { background-position: -6620px 0px; }
+.emoji-1F34D { background-position: -6640px 0px; }
+.emoji-1F34E { background-position: -6660px 0px; }
+.emoji-1F34F { background-position: -6680px 0px; }
+.emoji-1F350 { background-position: -6700px 0px; }
+.emoji-1F351 { background-position: -6720px 0px; }
+.emoji-1F352 { background-position: -6740px 0px; }
+.emoji-1F353 { background-position: -6760px 0px; }
+.emoji-1F354 { background-position: -6780px 0px; }
+.emoji-1F355 { background-position: -6800px 0px; }
+.emoji-1F356 { background-position: -6820px 0px; }
+.emoji-1F357 { background-position: -6840px 0px; }
+.emoji-1F358 { background-position: -6860px 0px; }
+.emoji-1F359 { background-position: -6880px 0px; }
+.emoji-1F35A { background-position: -6900px 0px; }
+.emoji-1F35B { background-position: -6920px 0px; }
+.emoji-1F35C { background-position: -6940px 0px; }
+.emoji-1F35D { background-position: -6960px 0px; }
+.emoji-1F35E { background-position: -6980px 0px; }
+.emoji-1F35F { background-position: -7000px 0px; }
+.emoji-1F360 { background-position: -7020px 0px; }
+.emoji-1F361 { background-position: -7040px 0px; }
+.emoji-1F362 { background-position: -7060px 0px; }
+.emoji-1F363 { background-position: -7080px 0px; }
+.emoji-1F364 { background-position: -7100px 0px; }
+.emoji-1F365 { background-position: -7120px 0px; }
+.emoji-1F366 { background-position: -7140px 0px; }
+.emoji-1F367 { background-position: -7160px 0px; }
+.emoji-1F368 { background-position: -7180px 0px; }
+.emoji-1F369 { background-position: -7200px 0px; }
+.emoji-1F36A { background-position: -7220px 0px; }
+.emoji-1F36B { background-position: -7240px 0px; }
+.emoji-1F36C { background-position: -7260px 0px; }
+.emoji-1F36D { background-position: -7280px 0px; }
+.emoji-1F36E { background-position: -7300px 0px; }
+.emoji-1F36F { background-position: -7320px 0px; }
+.emoji-1F370 { background-position: -7340px 0px; }
+.emoji-1F371 { background-position: -7360px 0px; }
+.emoji-1F372 { background-position: -7380px 0px; }
+.emoji-1F373 { background-position: -7400px 0px; }
+.emoji-1F374 { background-position: -7420px 0px; }
+.emoji-1F375 { background-position: -7440px 0px; }
+.emoji-1F376 { background-position: -7460px 0px; }
+.emoji-1F377 { background-position: -7480px 0px; }
+.emoji-1F378 { background-position: -7500px 0px; }
+.emoji-1F379 { background-position: -7520px 0px; }
+.emoji-1F37A { background-position: -7540px 0px; }
+.emoji-1F37B { background-position: -7560px 0px; }
+.emoji-1F37C { background-position: -7580px 0px; }
+.emoji-1F37D { background-position: -7600px 0px; }
+.emoji-1F380 { background-position: -7620px 0px; }
+.emoji-1F381 { background-position: -7640px 0px; }
+.emoji-1F382 { background-position: -7660px 0px; }
+.emoji-1F383 { background-position: -7680px 0px; }
+.emoji-1F384 { background-position: -7700px 0px; }
+.emoji-1F385 { background-position: -7720px 0px; }
+.emoji-1F386 { background-position: -7740px 0px; }
+.emoji-1F387 { background-position: -7760px 0px; }
+.emoji-1F388 { background-position: -7780px 0px; }
+.emoji-1F389 { background-position: -7800px 0px; }
+.emoji-1F38A { background-position: -7820px 0px; }
+.emoji-1F38B { background-position: -7840px 0px; }
+.emoji-1F38C { background-position: -7860px 0px; }
+.emoji-1F38D { background-position: -7880px 0px; }
+.emoji-1F38E { background-position: -7900px 0px; }
+.emoji-1F38F { background-position: -7920px 0px; }
+.emoji-1F390 { background-position: -7940px 0px; }
+.emoji-1F391 { background-position: -7960px 0px; }
+.emoji-1F392 { background-position: -7980px 0px; }
+.emoji-1F393 { background-position: -8000px 0px; }
+.emoji-1F394 { background-position: -8020px 0px; }
+.emoji-1F395 { background-position: -8040px 0px; }
+.emoji-1F396 { background-position: -8060px 0px; }
+.emoji-1F397 { background-position: -8080px 0px; }
+.emoji-1F398 { background-position: -8100px 0px; }
+.emoji-1F399 { background-position: -8120px 0px; }
+.emoji-1F39A { background-position: -8140px 0px; }
+.emoji-1F39B { background-position: -8160px 0px; }
+.emoji-1F39C { background-position: -8180px 0px; }
+.emoji-1F39D { background-position: -8200px 0px; }
+.emoji-1F39E { background-position: -8220px 0px; }
+.emoji-1F39F { background-position: -8240px 0px; }
+.emoji-1F3A0 { background-position: -8260px 0px; }
+.emoji-1F3A1 { background-position: -8280px 0px; }
+.emoji-1F3A2 { background-position: -8300px 0px; }
+.emoji-1F3A3 { background-position: -8320px 0px; }
+.emoji-1F3A4 { background-position: -8340px 0px; }
+.emoji-1F3A5 { background-position: -8360px 0px; }
+.emoji-1F3A6 { background-position: -8380px 0px; }
+.emoji-1F3A7 { background-position: -8400px 0px; }
+.emoji-1F3A8 { background-position: -8420px 0px; }
+.emoji-1F3A9 { background-position: -8440px 0px; }
+.emoji-1F3AA { background-position: -8460px 0px; }
+.emoji-1F3AB { background-position: -8480px 0px; }
+.emoji-1F3AC { background-position: -8500px 0px; }
+.emoji-1F3AD { background-position: -8520px 0px; }
+.emoji-1F3AE { background-position: -8540px 0px; }
+.emoji-1F3AF { background-position: -8560px 0px; }
+.emoji-1F3B0 { background-position: -8580px 0px; }
+.emoji-1F3B1 { background-position: -8600px 0px; }
+.emoji-1F3B2 { background-position: -8620px 0px; }
+.emoji-1F3B3 { background-position: -8640px 0px; }
+.emoji-1F3B4 { background-position: -8660px 0px; }
+.emoji-1F3B5 { background-position: -8680px 0px; }
+.emoji-1F3B6 { background-position: -8700px 0px; }
+.emoji-1F3B7 { background-position: -8720px 0px; }
+.emoji-1F3B8 { background-position: -8740px 0px; }
+.emoji-1F3B9 { background-position: -8760px 0px; }
+.emoji-1F3BA { background-position: -8780px 0px; }
+.emoji-1F3BB { background-position: -8800px 0px; }
+.emoji-1F3BC { background-position: -8820px 0px; }
+.emoji-1F3BD { background-position: -8840px 0px; }
+.emoji-1F3BE { background-position: -8860px 0px; }
+.emoji-1F3BF { background-position: -8880px 0px; }
+.emoji-1F3C0 { background-position: -8900px 0px; }
+.emoji-1F3C1 { background-position: -8920px 0px; }
+.emoji-1F3C2 { background-position: -8940px 0px; }
+.emoji-1F3C3 { background-position: -8960px 0px; }
+.emoji-1F3C4 { background-position: -8980px 0px; }
+.emoji-1F3C5 { background-position: -9000px 0px; }
+.emoji-1F3C6 { background-position: -9020px 0px; }
+.emoji-1F3C7 { background-position: -9040px 0px; }
+.emoji-1F3C8 { background-position: -9060px 0px; }
+.emoji-1F3C9 { background-position: -9080px 0px; }
+.emoji-1F3CA { background-position: -9100px 0px; }
+.emoji-1F3CB { background-position: -9120px 0px; }
+.emoji-1F3CC { background-position: -9140px 0px; }
+.emoji-1F3CD { background-position: -9160px 0px; }
+.emoji-1F3CE { background-position: -9180px 0px; }
+.emoji-1F3D4 { background-position: -9200px 0px; }
+.emoji-1F3D5 { background-position: -9220px 0px; }
+.emoji-1F3D6 { background-position: -9240px 0px; }
+.emoji-1F3D7 { background-position: -9260px 0px; }
+.emoji-1F3D8 { background-position: -9280px 0px; }
+.emoji-1F3D9 { background-position: -9300px 0px; }
+.emoji-1F3DA { background-position: -9320px 0px; }
+.emoji-1F3DB { background-position: -9340px 0px; }
+.emoji-1F3DC { background-position: -9360px 0px; }
+.emoji-1F3DD { background-position: -9380px 0px; }
+.emoji-1F3DE { background-position: -9400px 0px; }
+.emoji-1F3DF { background-position: -9420px 0px; }
+.emoji-1F3E0 { background-position: -9440px 0px; }
+.emoji-1F3E1 { background-position: -9460px 0px; }
+.emoji-1F3E2 { background-position: -9480px 0px; }
+.emoji-1F3E3 { background-position: -9500px 0px; }
+.emoji-1F3E4 { background-position: -9520px 0px; }
+.emoji-1F3E5 { background-position: -9540px 0px; }
+.emoji-1F3E6 { background-position: -9560px 0px; }
+.emoji-1F3E7 { background-position: -9580px 0px; }
+.emoji-1F3E8 { background-position: -9600px 0px; }
+.emoji-1F3E9 { background-position: -9620px 0px; }
+.emoji-1F3EA { background-position: -9640px 0px; }
+.emoji-1F3EB { background-position: -9660px 0px; }
+.emoji-1F3EC { background-position: -9680px 0px; }
+.emoji-1F3ED { background-position: -9700px 0px; }
+.emoji-1F3EE { background-position: -9720px 0px; }
+.emoji-1F3EF { background-position: -9740px 0px; }
+.emoji-1F3F0 { background-position: -9760px 0px; }
+.emoji-1F3F1 { background-position: -9780px 0px; }
+.emoji-1F3F2 { background-position: -9800px 0px; }
+.emoji-1F3F3 { background-position: -9820px 0px; }
+.emoji-1F3F4 { background-position: -9840px 0px; }
+.emoji-1F3F5 { background-position: -9860px 0px; }
+.emoji-1F3F6 { background-position: -9880px 0px; }
+.emoji-1F3F7 { background-position: -9900px 0px; }
+.emoji-1F400 { background-position: -9920px 0px; }
+.emoji-1F401 { background-position: -9940px 0px; }
+.emoji-1F402 { background-position: -9960px 0px; }
+.emoji-1F403 { background-position: -9980px 0px; }
+.emoji-1F404 { background-position: -10000px 0px; }
+.emoji-1F405 { background-position: -10020px 0px; }
+.emoji-1F406 { background-position: -10040px 0px; }
+.emoji-1F407 { background-position: -10060px 0px; }
+.emoji-1F408 { background-position: -10080px 0px; }
+.emoji-1F409 { background-position: -10100px 0px; }
+.emoji-1F40A { background-position: -10120px 0px; }
+.emoji-1F40B { background-position: -10140px 0px; }
+.emoji-1F40C { background-position: -10160px 0px; }
+.emoji-1F40D { background-position: -10180px 0px; }
+.emoji-1F40E { background-position: -10200px 0px; }
+.emoji-1F40F { background-position: -10220px 0px; }
+.emoji-1F410 { background-position: -10240px 0px; }
+.emoji-1F411 { background-position: -10260px 0px; }
+.emoji-1F412 { background-position: -10280px 0px; }
+.emoji-1F413 { background-position: -10300px 0px; }
+.emoji-1F414 { background-position: -10320px 0px; }
+.emoji-1F415 { background-position: -10340px 0px; }
+.emoji-1F416 { background-position: -10360px 0px; }
+.emoji-1F417 { background-position: -10380px 0px; }
+.emoji-1F418 { background-position: -10400px 0px; }
+.emoji-1F419 { background-position: -10420px 0px; }
+.emoji-1F41A { background-position: -10440px 0px; }
+.emoji-1F41B { background-position: -10460px 0px; }
+.emoji-1F41C { background-position: -10480px 0px; }
+.emoji-1F41D { background-position: -10500px 0px; }
+.emoji-1F41E { background-position: -10520px 0px; }
+.emoji-1F41F { background-position: -10540px 0px; }
+.emoji-1F420 { background-position: -10560px 0px; }
+.emoji-1F421 { background-position: -10580px 0px; }
+.emoji-1F422 { background-position: -10600px 0px; }
+.emoji-1F423 { background-position: -10620px 0px; }
+.emoji-1F424 { background-position: -10640px 0px; }
+.emoji-1F425 { background-position: -10660px 0px; }
+.emoji-1F426 { background-position: -10680px 0px; }
+.emoji-1F427 { background-position: -10700px 0px; }
+.emoji-1F428 { background-position: -10720px 0px; }
+.emoji-1F429 { background-position: -10740px 0px; }
+.emoji-1F42A { background-position: -10760px 0px; }
+.emoji-1F42B { background-position: -10780px 0px; }
+.emoji-1F42C { background-position: -10800px 0px; }
+.emoji-1F42D { background-position: -10820px 0px; }
+.emoji-1F42E { background-position: -10840px 0px; }
+.emoji-1F42F { background-position: -10860px 0px; }
+.emoji-1F430 { background-position: -10880px 0px; }
+.emoji-1F431 { background-position: -10900px 0px; }
+.emoji-1F432 { background-position: -10920px 0px; }
+.emoji-1F433 { background-position: -10940px 0px; }
+.emoji-1F434 { background-position: -10960px 0px; }
+.emoji-1F435 { background-position: -10980px 0px; }
+.emoji-1F436 { background-position: -11000px 0px; }
+.emoji-1F437 { background-position: -11020px 0px; }
+.emoji-1F438 { background-position: -11040px 0px; }
+.emoji-1F439 { background-position: -11060px 0px; }
+.emoji-1F43A { background-position: -11080px 0px; }
+.emoji-1F43B { background-position: -11100px 0px; }
+.emoji-1F43C { background-position: -11120px 0px; }
+.emoji-1F43D { background-position: -11140px 0px; }
+.emoji-1F43E { background-position: -11160px 0px; }
+.emoji-1F43F { background-position: -11180px 0px; }
+.emoji-1F440 { background-position: -11200px 0px; }
+.emoji-1F441 { background-position: -11220px 0px; }
+.emoji-1F442 { background-position: -11240px 0px; }
+.emoji-1F443 { background-position: -11260px 0px; }
+.emoji-1F444 { background-position: -11280px 0px; }
+.emoji-1F445 { background-position: -11300px 0px; }
+.emoji-1F446 { background-position: -11320px 0px; }
+.emoji-1F447 { background-position: -11340px 0px; }
+.emoji-1F448 { background-position: -11360px 0px; }
+.emoji-1F449 { background-position: -11380px 0px; }
+.emoji-1F44A { background-position: -11400px 0px; }
+.emoji-1F44B { background-position: -11420px 0px; }
+.emoji-1F44C { background-position: -11440px 0px; }
+.emoji-1F44D { background-position: -11460px 0px; }
+.emoji-1F44E { background-position: -11480px 0px; }
+.emoji-1F44F { background-position: -11500px 0px; }
+.emoji-1F450 { background-position: -11520px 0px; }
+.emoji-1F451 { background-position: -11540px 0px; }
+.emoji-1F452 { background-position: -11560px 0px; }
+.emoji-1F453 { background-position: -11580px 0px; }
+.emoji-1F454 { background-position: -11600px 0px; }
+.emoji-1F455 { background-position: -11620px 0px; }
+.emoji-1F456 { background-position: -11640px 0px; }
+.emoji-1F457 { background-position: -11660px 0px; }
+.emoji-1F458 { background-position: -11680px 0px; }
+.emoji-1F459 { background-position: -11700px 0px; }
+.emoji-1F45A { background-position: -11720px 0px; }
+.emoji-1F45B { background-position: -11740px 0px; }
+.emoji-1F45C { background-position: -11760px 0px; }
+.emoji-1F45D { background-position: -11780px 0px; }
+.emoji-1F45E { background-position: -11800px 0px; }
+.emoji-1F45F { background-position: -11820px 0px; }
+.emoji-1F460 { background-position: -11840px 0px; }
+.emoji-1F461 { background-position: -11860px 0px; }
+.emoji-1F462 { background-position: -11880px 0px; }
+.emoji-1F463 { background-position: -11900px 0px; }
+.emoji-1F464 { background-position: -11920px 0px; }
+.emoji-1F465 { background-position: -11940px 0px; }
+.emoji-1F466 { background-position: -11960px 0px; }
+.emoji-1F467 { background-position: -11980px 0px; }
+.emoji-1F468 { background-position: -12000px 0px; }
+.emoji-1F468-1F468-1F466 { background-position: -12020px 0px; }
+.emoji-1F468-1F468-1F466-1F466 { background-position: -12040px 0px; }
+.emoji-1F468-1F468-1F467 { background-position: -12060px 0px; }
+.emoji-1F468-1F468-1F467-1F466 { background-position: -12080px 0px; }
+.emoji-1F468-1F468-1F467-1F467 { background-position: -12100px 0px; }
+.emoji-1F468-1F469-1F466-1F466 { background-position: -12120px 0px; }
+.emoji-1F468-1F469-1F467 { background-position: -12140px 0px; }
+.emoji-1F468-1F469-1F467-1F466 { background-position: -12160px 0px; }
+.emoji-1F468-1F469-1F467-1F467 { background-position: -12180px 0px; }
+.emoji-1F468-2764-1F468 { background-position: -12200px 0px; }
+.emoji-1F468-2764-1F48B-1F468 { background-position: -12220px 0px; }
+.emoji-1F469 { background-position: -12240px 0px; }
+.emoji-1F469-1F469-1F466 { background-position: -12260px 0px; }
+.emoji-1F469-1F469-1F466-1F466 { background-position: -12280px 0px; }
+.emoji-1F469-1F469-1F467 { background-position: -12300px 0px; }
+.emoji-1F469-1F469-1F467-1F466 { background-position: -12320px 0px; }
+.emoji-1F469-1F469-1F467-1F467 { background-position: -12340px 0px; }
+.emoji-1F469-2764-1F469 { background-position: -12360px 0px; }
+.emoji-1F469-2764-1F48B-1F469 { background-position: -12380px 0px; }
+.emoji-1F46A { background-position: -12400px 0px; }
+.emoji-1F46B { background-position: -12420px 0px; }
+.emoji-1F46C { background-position: -12440px 0px; }
+.emoji-1F46D { background-position: -12460px 0px; }
+.emoji-1F46E { background-position: -12480px 0px; }
+.emoji-1F46F { background-position: -12500px 0px; }
+.emoji-1F470 { background-position: -12520px 0px; }
+.emoji-1F471 { background-position: -12540px 0px; }
+.emoji-1F472 { background-position: -12560px 0px; }
+.emoji-1F473 { background-position: -12580px 0px; }
+.emoji-1F474 { background-position: -12600px 0px; }
+.emoji-1F475 { background-position: -12620px 0px; }
+.emoji-1F476 { background-position: -12640px 0px; }
+.emoji-1F477 { background-position: -12660px 0px; }
+.emoji-1F478 { background-position: -12680px 0px; }
+.emoji-1F479 { background-position: -12700px 0px; }
+.emoji-1F47A { background-position: -12720px 0px; }
+.emoji-1F47B { background-position: -12740px 0px; }
+.emoji-1F47C { background-position: -12760px 0px; }
+.emoji-1F47D { background-position: -12780px 0px; }
+.emoji-1F47E { background-position: -12800px 0px; }
+.emoji-1F47F { background-position: -12820px 0px; }
+.emoji-1F480 { background-position: -12840px 0px; }
+.emoji-1F481 { background-position: -12860px 0px; }
+.emoji-1F482 { background-position: -12880px 0px; }
+.emoji-1F483 { background-position: -12900px 0px; }
+.emoji-1F484 { background-position: -12920px 0px; }
+.emoji-1F485 { background-position: -12940px 0px; }
+.emoji-1F486 { background-position: -12960px 0px; }
+.emoji-1F487 { background-position: -12980px 0px; }
+.emoji-1F488 { background-position: -13000px 0px; }
+.emoji-1F489 { background-position: -13020px 0px; }
+.emoji-1F48A { background-position: -13040px 0px; }
+.emoji-1F48B { background-position: -13060px 0px; }
+.emoji-1F48C { background-position: -13080px 0px; }
+.emoji-1F48D { background-position: -13100px 0px; }
+.emoji-1F48E { background-position: -13120px 0px; }
+.emoji-1F48F { background-position: -13140px 0px; }
+.emoji-1F490 { background-position: -13160px 0px; }
+.emoji-1F491 { background-position: -13180px 0px; }
+.emoji-1F492 { background-position: -13200px 0px; }
+.emoji-1F493 { background-position: -13220px 0px; }
+.emoji-1F494 { background-position: -13240px 0px; }
+.emoji-1F495 { background-position: -13260px 0px; }
+.emoji-1F496 { background-position: -13280px 0px; }
+.emoji-1F497 { background-position: -13300px 0px; }
+.emoji-1F498 { background-position: -13320px 0px; }
+.emoji-1F499 { background-position: -13340px 0px; }
+.emoji-1F49A { background-position: -13360px 0px; }
+.emoji-1F49B { background-position: -13380px 0px; }
+.emoji-1F49C { background-position: -13400px 0px; }
+.emoji-1F49D { background-position: -13420px 0px; }
+.emoji-1F49E { background-position: -13440px 0px; }
+.emoji-1F49F { background-position: -13460px 0px; }
+.emoji-1F4A0 { background-position: -13480px 0px; }
+.emoji-1F4A1 { background-position: -13500px 0px; }
+.emoji-1F4A2 { background-position: -13520px 0px; }
+.emoji-1F4A3 { background-position: -13540px 0px; }
+.emoji-1F4A4 { background-position: -13560px 0px; }
+.emoji-1F4A5 { background-position: -13580px 0px; }
+.emoji-1F4A6 { background-position: -13600px 0px; }
+.emoji-1F4A7 { background-position: -13620px 0px; }
+.emoji-1F4A8 { background-position: -13640px 0px; }
+.emoji-1F4A9 { background-position: -13660px 0px; }
+.emoji-1F4AA { background-position: -13680px 0px; }
+.emoji-1F4AB { background-position: -13700px 0px; }
+.emoji-1F4AC { background-position: -13720px 0px; }
+.emoji-1F4AD { background-position: -13740px 0px; }
+.emoji-1F4AE { background-position: -13760px 0px; }
+.emoji-1F4AF { background-position: -13780px 0px; }
+.emoji-1F4B0 { background-position: -13800px 0px; }
+.emoji-1F4B1 { background-position: -13820px 0px; }
+.emoji-1F4B2 { background-position: -13840px 0px; }
+.emoji-1F4B3 { background-position: -13860px 0px; }
+.emoji-1F4B4 { background-position: -13880px 0px; }
+.emoji-1F4B5 { background-position: -13900px 0px; }
+.emoji-1F4B6 { background-position: -13920px 0px; }
+.emoji-1F4B7 { background-position: -13940px 0px; }
+.emoji-1F4B8 { background-position: -13960px 0px; }
+.emoji-1F4B9 { background-position: -13980px 0px; }
+.emoji-1F4BA { background-position: -14000px 0px; }
+.emoji-1F4BB { background-position: -14020px 0px; }
+.emoji-1F4BC { background-position: -14040px 0px; }
+.emoji-1F4BD { background-position: -14060px 0px; }
+.emoji-1F4BE { background-position: -14080px 0px; }
+.emoji-1F4BF { background-position: -14100px 0px; }
+.emoji-1F4C0 { background-position: -14120px 0px; }
+.emoji-1F4C1 { background-position: -14140px 0px; }
+.emoji-1F4C2 { background-position: -14160px 0px; }
+.emoji-1F4C3 { background-position: -14180px 0px; }
+.emoji-1F4C4 { background-position: -14200px 0px; }
+.emoji-1F4C5 { background-position: -14220px 0px; }
+.emoji-1F4C6 { background-position: -14240px 0px; }
+.emoji-1F4C7 { background-position: -14260px 0px; }
+.emoji-1F4C8 { background-position: -14280px 0px; }
+.emoji-1F4C9 { background-position: -14300px 0px; }
+.emoji-1F4CA { background-position: -14320px 0px; }
+.emoji-1F4CB { background-position: -14340px 0px; }
+.emoji-1F4CC { background-position: -14360px 0px; }
+.emoji-1F4CD { background-position: -14380px 0px; }
+.emoji-1F4CE { background-position: -14400px 0px; }
+.emoji-1F4CF { background-position: -14420px 0px; }
+.emoji-1F4D0 { background-position: -14440px 0px; }
+.emoji-1F4D1 { background-position: -14460px 0px; }
+.emoji-1F4D2 { background-position: -14480px 0px; }
+.emoji-1F4D3 { background-position: -14500px 0px; }
+.emoji-1F4D4 { background-position: -14520px 0px; }
+.emoji-1F4D5 { background-position: -14540px 0px; }
+.emoji-1F4D6 { background-position: -14560px 0px; }
+.emoji-1F4D7 { background-position: -14580px 0px; }
+.emoji-1F4D8 { background-position: -14600px 0px; }
+.emoji-1F4D9 { background-position: -14620px 0px; }
+.emoji-1F4DA { background-position: -14640px 0px; }
+.emoji-1F4DB { background-position: -14660px 0px; }
+.emoji-1F4DC { background-position: -14680px 0px; }
+.emoji-1F4DD { background-position: -14700px 0px; }
+.emoji-1F4DE { background-position: -14720px 0px; }
+.emoji-1F4DF { background-position: -14740px 0px; }
+.emoji-1F4E0 { background-position: -14760px 0px; }
+.emoji-1F4E1 { background-position: -14780px 0px; }
+.emoji-1F4E2 { background-position: -14800px 0px; }
+.emoji-1F4E3 { background-position: -14820px 0px; }
+.emoji-1F4E4 { background-position: -14840px 0px; }
+.emoji-1F4E5 { background-position: -14860px 0px; }
+.emoji-1F4E6 { background-position: -14880px 0px; }
+.emoji-1F4E7 { background-position: -14900px 0px; }
+.emoji-1F4E8 { background-position: -14920px 0px; }
+.emoji-1F4E9 { background-position: -14940px 0px; }
+.emoji-1F4EA { background-position: -14960px 0px; }
+.emoji-1F4EB { background-position: -14980px 0px; }
+.emoji-1F4EC { background-position: -15000px 0px; }
+.emoji-1F4ED { background-position: -15020px 0px; }
+.emoji-1F4EE { background-position: -15040px 0px; }
+.emoji-1F4EF { background-position: -15060px 0px; }
+.emoji-1F4F0 { background-position: -15080px 0px; }
+.emoji-1F4F1 { background-position: -15100px 0px; }
+.emoji-1F4F2 { background-position: -15120px 0px; }
+.emoji-1F4F3 { background-position: -15140px 0px; }
+.emoji-1F4F4 { background-position: -15160px 0px; }
+.emoji-1F4F5 { background-position: -15180px 0px; }
+.emoji-1F4F6 { background-position: -15200px 0px; }
+.emoji-1F4F7 { background-position: -15220px 0px; }
+.emoji-1F4F8 { background-position: -15240px 0px; }
+.emoji-1F4F9 { background-position: -15260px 0px; }
+.emoji-1F4FA { background-position: -15280px 0px; }
+.emoji-1F4FB { background-position: -15300px 0px; }
+.emoji-1F4FC { background-position: -15320px 0px; }
+.emoji-1F4FD { background-position: -15340px 0px; }
+.emoji-1F4FE { background-position: -15360px 0px; }
+.emoji-1F500 { background-position: -15380px 0px; }
+.emoji-1F501 { background-position: -15400px 0px; }
+.emoji-1F502 { background-position: -15420px 0px; }
+.emoji-1F503 { background-position: -15440px 0px; }
+.emoji-1F504 { background-position: -15460px 0px; }
+.emoji-1F505 { background-position: -15480px 0px; }
+.emoji-1F506 { background-position: -15500px 0px; }
+.emoji-1F507 { background-position: -15520px 0px; }
+.emoji-1F508 { background-position: -15540px 0px; }
+.emoji-1F509 { background-position: -15560px 0px; }
+.emoji-1F50A { background-position: -15580px 0px; }
+.emoji-1F50B { background-position: -15600px 0px; }
+.emoji-1F50C { background-position: -15620px 0px; }
+.emoji-1F50D { background-position: -15640px 0px; }
+.emoji-1F50E { background-position: -15660px 0px; }
+.emoji-1F50F { background-position: -15680px 0px; }
+.emoji-1F510 { background-position: -15700px 0px; }
+.emoji-1F511 { background-position: -15720px 0px; }
+.emoji-1F512 { background-position: -15740px 0px; }
+.emoji-1F513 { background-position: -15760px 0px; }
+.emoji-1F514 { background-position: -15780px 0px; }
+.emoji-1F515 { background-position: -15800px 0px; }
+.emoji-1F516 { background-position: -15820px 0px; }
+.emoji-1F517 { background-position: -15840px 0px; }
+.emoji-1F518 { background-position: -15860px 0px; }
+.emoji-1F519 { background-position: -15880px 0px; }
+.emoji-1F51A { background-position: -15900px 0px; }
+.emoji-1F51B { background-position: -15920px 0px; }
+.emoji-1F51C { background-position: -15940px 0px; }
+.emoji-1F51D { background-position: -15960px 0px; }
+.emoji-1F51E { background-position: -15980px 0px; }
+.emoji-1F51F { background-position: -16000px 0px; }
+.emoji-1F520 { background-position: -16020px 0px; }
+.emoji-1F521 { background-position: -16040px 0px; }
+.emoji-1F522 { background-position: -16060px 0px; }
+.emoji-1F523 { background-position: -16080px 0px; }
+.emoji-1F524 { background-position: -16100px 0px; }
+.emoji-1F525 { background-position: -16120px 0px; }
+.emoji-1F526 { background-position: -16140px 0px; }
+.emoji-1F527 { background-position: -16160px 0px; }
+.emoji-1F528 { background-position: -16180px 0px; }
+.emoji-1F529 { background-position: -16200px 0px; }
+.emoji-1F52A { background-position: -16220px 0px; }
+.emoji-1F52B { background-position: -16240px 0px; }
+.emoji-1F52C { background-position: -16260px 0px; }
+.emoji-1F52D { background-position: -16280px 0px; }
+.emoji-1F52E { background-position: -16300px 0px; }
+.emoji-1F52F { background-position: -16320px 0px; }
+.emoji-1F530 { background-position: -16340px 0px; }
+.emoji-1F531 { background-position: -16360px 0px; }
+.emoji-1F532 { background-position: -16380px 0px; }
+.emoji-1F533 { background-position: -16400px 0px; }
+.emoji-1F534 { background-position: -16420px 0px; }
+.emoji-1F535 { background-position: -16440px 0px; }
+.emoji-1F536 { background-position: -16460px 0px; }
+.emoji-1F537 { background-position: -16480px 0px; }
+.emoji-1F538 { background-position: -16500px 0px; }
+.emoji-1F539 { background-position: -16520px 0px; }
+.emoji-1F53A { background-position: -16540px 0px; }
+.emoji-1F53B { background-position: -16560px 0px; }
+.emoji-1F53C { background-position: -16580px 0px; }
+.emoji-1F53D { background-position: -16600px 0px; }
+.emoji-1F546 { background-position: -16620px 0px; }
+.emoji-1F547 { background-position: -16640px 0px; }
+.emoji-1F548 { background-position: -16660px 0px; }
+.emoji-1F549 { background-position: -16680px 0px; }
+.emoji-1F54A { background-position: -16700px 0px; }
+.emoji-1F550 { background-position: -16720px 0px; }
+.emoji-1F551 { background-position: -16740px 0px; }
+.emoji-1F552 { background-position: -16760px 0px; }
+.emoji-1F553 { background-position: -16780px 0px; }
+.emoji-1F554 { background-position: -16800px 0px; }
+.emoji-1F555 { background-position: -16820px 0px; }
+.emoji-1F556 { background-position: -16840px 0px; }
+.emoji-1F557 { background-position: -16860px 0px; }
+.emoji-1F558 { background-position: -16880px 0px; }
+.emoji-1F559 { background-position: -16900px 0px; }
+.emoji-1F55A { background-position: -16920px 0px; }
+.emoji-1F55B { background-position: -16940px 0px; }
+.emoji-1F55C { background-position: -16960px 0px; }
+.emoji-1F55D { background-position: -16980px 0px; }
+.emoji-1F55E { background-position: -17000px 0px; }
+.emoji-1F55F { background-position: -17020px 0px; }
+.emoji-1F560 { background-position: -17040px 0px; }
+.emoji-1F561 { background-position: -17060px 0px; }
+.emoji-1F562 { background-position: -17080px 0px; }
+.emoji-1F563 { background-position: -17100px 0px; }
+.emoji-1F564 { background-position: -17120px 0px; }
+.emoji-1F565 { background-position: -17140px 0px; }
+.emoji-1F566 { background-position: -17160px 0px; }
+.emoji-1F567 { background-position: -17180px 0px; }
+.emoji-1F568 { background-position: -17200px 0px; }
+.emoji-1F569 { background-position: -17220px 0px; }
+.emoji-1F56A { background-position: -17240px 0px; }
+.emoji-1F56B { background-position: -17260px 0px; }
+.emoji-1F56C { background-position: -17280px 0px; }
+.emoji-1F56D { background-position: -17300px 0px; }
+.emoji-1F56E { background-position: -17320px 0px; }
+.emoji-1F56F { background-position: -17340px 0px; }
+.emoji-1F570 { background-position: -17360px 0px; }
+.emoji-1F571 { background-position: -17380px 0px; }
+.emoji-1F572 { background-position: -17400px 0px; }
+.emoji-1F573 { background-position: -17420px 0px; }
+.emoji-1F574 { background-position: -17440px 0px; }
+.emoji-1F575 { background-position: -17460px 0px; }
+.emoji-1F576 { background-position: -17480px 0px; }
+.emoji-1F577 { background-position: -17500px 0px; }
+.emoji-1F578 { background-position: -17520px 0px; }
+.emoji-1F579 { background-position: -17540px 0px; }
+.emoji-1F57B { background-position: -17560px 0px; }
+.emoji-1F57E { background-position: -17580px 0px; }
+.emoji-1F57F { background-position: -17600px 0px; }
+.emoji-1F581 { background-position: -17620px 0px; }
+.emoji-1F582 { background-position: -17640px 0px; }
+.emoji-1F583 { background-position: -17660px 0px; }
+.emoji-1F585 { background-position: -17680px 0px; }
+.emoji-1F586 { background-position: -17700px 0px; }
+.emoji-1F587 { background-position: -17720px 0px; }
+.emoji-1F588 { background-position: -17740px 0px; }
+.emoji-1F589 { background-position: -17760px 0px; }
+.emoji-1F58A { background-position: -17780px 0px; }
+.emoji-1F58B { background-position: -17800px 0px; }
+.emoji-1F58C { background-position: -17820px 0px; }
+.emoji-1F58D { background-position: -17840px 0px; }
+.emoji-1F58E { background-position: -17860px 0px; }
+.emoji-1F58F { background-position: -17880px 0px; }
+.emoji-1F590 { background-position: -17900px 0px; }
+.emoji-1F591 { background-position: -17920px 0px; }
+.emoji-1F592 { background-position: -17940px 0px; }
+.emoji-1F593 { background-position: -17960px 0px; }
+.emoji-1F594 { background-position: -17980px 0px; }
+.emoji-1F595 { background-position: -18000px 0px; }
+.emoji-1F596 { background-position: -18020px 0px; }
+.emoji-1F597 { background-position: -18040px 0px; }
+.emoji-1F598 { background-position: -18060px 0px; }
+.emoji-1F599 { background-position: -18080px 0px; }
+.emoji-1F59E { background-position: -18100px 0px; }
+.emoji-1F59F { background-position: -18120px 0px; }
+.emoji-1F5A5 { background-position: -18140px 0px; }
+.emoji-1F5A6 { background-position: -18160px 0px; }
+.emoji-1F5A7 { background-position: -18180px 0px; }
+.emoji-1F5A8 { background-position: -18200px 0px; }
+.emoji-1F5A9 { background-position: -18220px 0px; }
+.emoji-1F5AA { background-position: -18240px 0px; }
+.emoji-1F5AB { background-position: -18260px 0px; }
+.emoji-1F5AD { background-position: -18280px 0px; }
+.emoji-1F5AE { background-position: -18300px 0px; }
+.emoji-1F5AF { background-position: -18320px 0px; }
+.emoji-1F5B2 { background-position: -18340px 0px; }
+.emoji-1F5B3 { background-position: -18360px 0px; }
+.emoji-1F5B4 { background-position: -18380px 0px; }
+.emoji-1F5B8 { background-position: -18400px 0px; }
+.emoji-1F5B9 { background-position: -18420px 0px; }
+.emoji-1F5BC { background-position: -18440px 0px; }
+.emoji-1F5BD { background-position: -18460px 0px; }
+.emoji-1F5BE { background-position: -18480px 0px; }
+.emoji-1F5C0 { background-position: -18500px 0px; }
+.emoji-1F5C1 { background-position: -18520px 0px; }
+.emoji-1F5C2 { background-position: -18540px 0px; }
+.emoji-1F5C3 { background-position: -18560px 0px; }
+.emoji-1F5C4 { background-position: -18580px 0px; }
+.emoji-1F5C6 { background-position: -18600px 0px; }
+.emoji-1F5C7 { background-position: -18620px 0px; }
+.emoji-1F5C9 { background-position: -18640px 0px; }
+.emoji-1F5CA { background-position: -18660px 0px; }
+.emoji-1F5CE { background-position: -18680px 0px; }
+.emoji-1F5CF { background-position: -18700px 0px; }
+.emoji-1F5D0 { background-position: -18720px 0px; }
+.emoji-1F5D1 { background-position: -18740px 0px; }
+.emoji-1F5D2 { background-position: -18760px 0px; }
+.emoji-1F5D3 { background-position: -18780px 0px; }
+.emoji-1F5D4 { background-position: -18800px 0px; }
+.emoji-1F5D8 { background-position: -18820px 0px; }
+.emoji-1F5D9 { background-position: -18840px 0px; }
+.emoji-1F5DC { background-position: -18860px 0px; }
+.emoji-1F5DD { background-position: -18880px 0px; }
+.emoji-1F5DE { background-position: -18900px 0px; }
+.emoji-1F5E0 { background-position: -18920px 0px; }
+.emoji-1F5E1 { background-position: -18940px 0px; }
+.emoji-1F5E2 { background-position: -18960px 0px; }
+.emoji-1F5E3 { background-position: -18980px 0px; }
+.emoji-1F5E8 { background-position: -19000px 0px; }
+.emoji-1F5E9 { background-position: -19020px 0px; }
+.emoji-1F5EA { background-position: -19040px 0px; }
+.emoji-1F5EB { background-position: -19060px 0px; }
+.emoji-1F5EC { background-position: -19080px 0px; }
+.emoji-1F5ED { background-position: -19100px 0px; }
+.emoji-1F5EE { background-position: -19120px 0px; }
+.emoji-1F5EF { background-position: -19140px 0px; }
+.emoji-1F5F0 { background-position: -19160px 0px; }
+.emoji-1F5F1 { background-position: -19180px 0px; }
+.emoji-1F5F2 { background-position: -19200px 0px; }
+.emoji-1F5F3 { background-position: -19220px 0px; }
+.emoji-1F5F4 { background-position: -19240px 0px; }
+.emoji-1F5F5 { background-position: -19260px 0px; }
+.emoji-1F5F8 { background-position: -19280px 0px; }
+.emoji-1F5F9 { background-position: -19300px 0px; }
+.emoji-1F5FA { background-position: -19320px 0px; }
+.emoji-1F5FB { background-position: -19340px 0px; }
+.emoji-1F5FC { background-position: -19360px 0px; }
+.emoji-1F5FD { background-position: -19380px 0px; }
+.emoji-1F5FE { background-position: -19400px 0px; }
+.emoji-1F5FF { background-position: -19420px 0px; }
+.emoji-1F600 { background-position: -19440px 0px; }
+.emoji-1F601 { background-position: -19460px 0px; }
+.emoji-1F602 { background-position: -19480px 0px; }
+.emoji-1F603 { background-position: -19500px 0px; }
+.emoji-1F604 { background-position: -19520px 0px; }
+.emoji-1F605 { background-position: -19540px 0px; }
+.emoji-1F606 { background-position: -19560px 0px; }
+.emoji-1F607 { background-position: -19580px 0px; }
+.emoji-1F608 { background-position: -19600px 0px; }
+.emoji-1F609 { background-position: -19620px 0px; }
+.emoji-1F60A { background-position: -19640px 0px; }
+.emoji-1F60B { background-position: -19660px 0px; }
+.emoji-1F60C { background-position: -19680px 0px; }
+.emoji-1F60D { background-position: -19700px 0px; }
+.emoji-1F60E { background-position: -19720px 0px; }
+.emoji-1F60F { background-position: -19740px 0px; }
+.emoji-1F610 { background-position: -19760px 0px; }
+.emoji-1F611 { background-position: -19780px 0px; }
+.emoji-1F612 { background-position: -19800px 0px; }
+.emoji-1F613 { background-position: -19820px 0px; }
+.emoji-1F614 { background-position: -19840px 0px; }
+.emoji-1F615 { background-position: -19860px 0px; }
+.emoji-1F616 { background-position: -19880px 0px; }
+.emoji-1F617 { background-position: -19900px 0px; }
+.emoji-1F618 { background-position: -19920px 0px; }
+.emoji-1F619 { background-position: -19940px 0px; }
+.emoji-1F61A { background-position: -19960px 0px; }
+.emoji-1F61B { background-position: -19980px 0px; }
+.emoji-1F61C { background-position: -20000px 0px; }
+.emoji-1F61D { background-position: -20020px 0px; }
+.emoji-1F61E { background-position: -20040px 0px; }
+.emoji-1F61F { background-position: -20060px 0px; }
+.emoji-1F620 { background-position: -20080px 0px; }
+.emoji-1F621 { background-position: -20100px 0px; }
+.emoji-1F622 { background-position: -20120px 0px; }
+.emoji-1F623 { background-position: -20140px 0px; }
+.emoji-1F624 { background-position: -20160px 0px; }
+.emoji-1F625 { background-position: -20180px 0px; }
+.emoji-1F626 { background-position: -20200px 0px; }
+.emoji-1F627 { background-position: -20220px 0px; }
+.emoji-1F628 { background-position: -20240px 0px; }
+.emoji-1F629 { background-position: -20260px 0px; }
+.emoji-1F62A { background-position: -20280px 0px; }
+.emoji-1F62B { background-position: -20300px 0px; }
+.emoji-1F62C { background-position: -20320px 0px; }
+.emoji-1F62D { background-position: -20340px 0px; }
+.emoji-1F62E { background-position: -20360px 0px; }
+.emoji-1F62F { background-position: -20380px 0px; }
+.emoji-1F630 { background-position: -20400px 0px; }
+.emoji-1F631 { background-position: -20420px 0px; }
+.emoji-1F632 { background-position: -20440px 0px; }
+.emoji-1F633 { background-position: -20460px 0px; }
+.emoji-1F634 { background-position: -20480px 0px; }
+.emoji-1F635 { background-position: -20500px 0px; }
+.emoji-1F636 { background-position: -20520px 0px; }
+.emoji-1F637 { background-position: -20540px 0px; }
+.emoji-1F638 { background-position: -20560px 0px; }
+.emoji-1F639 { background-position: -20580px 0px; }
+.emoji-1F63A { background-position: -20600px 0px; }
+.emoji-1F63B { background-position: -20620px 0px; }
+.emoji-1F63C { background-position: -20640px 0px; }
+.emoji-1F63D { background-position: -20660px 0px; }
+.emoji-1F63E { background-position: -20680px 0px; }
+.emoji-1F63F { background-position: -20700px 0px; }
+.emoji-1F640 { background-position: -20720px 0px; }
+.emoji-1F641 { background-position: -20740px 0px; }
+.emoji-1F642 { background-position: -20760px 0px; }
+.emoji-1F645 { background-position: -20780px 0px; }
+.emoji-1F646 { background-position: -20800px 0px; }
+.emoji-1F647 { background-position: -20820px 0px; }
+.emoji-1F648 { background-position: -20840px 0px; }
+.emoji-1F649 { background-position: -20860px 0px; }
+.emoji-1F64A { background-position: -20880px 0px; }
+.emoji-1F64B { background-position: -20900px 0px; }
+.emoji-1F64C { background-position: -20920px 0px; }
+.emoji-1F64D { background-position: -20940px 0px; }
+.emoji-1F64E { background-position: -20960px 0px; }
+.emoji-1F64F { background-position: -20980px 0px; }
+.emoji-1F680 { background-position: -21000px 0px; }
+.emoji-1F681 { background-position: -21020px 0px; }
+.emoji-1F682 { background-position: -21040px 0px; }
+.emoji-1F683 { background-position: -21060px 0px; }
+.emoji-1F684 { background-position: -21080px 0px; }
+.emoji-1F685 { background-position: -21100px 0px; }
+.emoji-1F686 { background-position: -21120px 0px; }
+.emoji-1F687 { background-position: -21140px 0px; }
+.emoji-1F688 { background-position: -21160px 0px; }
+.emoji-1F689 { background-position: -21180px 0px; }
+.emoji-1F68A { background-position: -21200px 0px; }
+.emoji-1F68B { background-position: -21220px 0px; }
+.emoji-1F68C { background-position: -21240px 0px; }
+.emoji-1F68D { background-position: -21260px 0px; }
+.emoji-1F68E { background-position: -21280px 0px; }
+.emoji-1F68F { background-position: -21300px 0px; }
+.emoji-1F690 { background-position: -21320px 0px; }
+.emoji-1F691 { background-position: -21340px 0px; }
+.emoji-1F692 { background-position: -21360px 0px; }
+.emoji-1F693 { background-position: -21380px 0px; }
+.emoji-1F694 { background-position: -21400px 0px; }
+.emoji-1F695 { background-position: -21420px 0px; }
+.emoji-1F696 { background-position: -21440px 0px; }
+.emoji-1F697 { background-position: -21460px 0px; }
+.emoji-1F698 { background-position: -21480px 0px; }
+.emoji-1F699 { background-position: -21500px 0px; }
+.emoji-1F69A { background-position: -21520px 0px; }
+.emoji-1F69B { background-position: -21540px 0px; }
+.emoji-1F69C { background-position: -21560px 0px; }
+.emoji-1F69D { background-position: -21580px 0px; }
+.emoji-1F69E { background-position: -21600px 0px; }
+.emoji-1F69F { background-position: -21620px 0px; }
+.emoji-1F6A0 { background-position: -21640px 0px; }
+.emoji-1F6A1 { background-position: -21660px 0px; }
+.emoji-1F6A2 { background-position: -21680px 0px; }
+.emoji-1F6A3 { background-position: -21700px 0px; }
+.emoji-1F6A4 { background-position: -21720px 0px; }
+.emoji-1F6A5 { background-position: -21740px 0px; }
+.emoji-1F6A6 { background-position: -21760px 0px; }
+.emoji-1F6A7 { background-position: -21780px 0px; }
+.emoji-1F6A8 { background-position: -21800px 0px; }
+.emoji-1F6A9 { background-position: -21820px 0px; }
+.emoji-1F6AA { background-position: -21840px 0px; }
+.emoji-1F6AB { background-position: -21860px 0px; }
+.emoji-1F6AC { background-position: -21880px 0px; }
+.emoji-1F6AD { background-position: -21900px 0px; }
+.emoji-1F6AE { background-position: -21920px 0px; }
+.emoji-1F6AF { background-position: -21940px 0px; }
+.emoji-1F6B0 { background-position: -21960px 0px; }
+.emoji-1F6B1 { background-position: -21980px 0px; }
+.emoji-1F6B2 { background-position: -22000px 0px; }
+.emoji-1F6B3 { background-position: -22020px 0px; }
+.emoji-1F6B4 { background-position: -22040px 0px; }
+.emoji-1F6B5 { background-position: -22060px 0px; }
+.emoji-1F6B6 { background-position: -22080px 0px; }
+.emoji-1F6B7 { background-position: -22100px 0px; }
+.emoji-1F6B8 { background-position: -22120px 0px; }
+.emoji-1F6B9 { background-position: -22140px 0px; }
+.emoji-1F6BA { background-position: -22160px 0px; }
+.emoji-1F6BB { background-position: -22180px 0px; }
+.emoji-1F6BC { background-position: -22200px 0px; }
+.emoji-1F6BD { background-position: -22220px 0px; }
+.emoji-1F6BE { background-position: -22240px 0px; }
+.emoji-1F6BF { background-position: -22260px 0px; }
+.emoji-1F6C0 { background-position: -22280px 0px; }
+.emoji-1F6C1 { background-position: -22300px 0px; }
+.emoji-1F6C2 { background-position: -22320px 0px; }
+.emoji-1F6C3 { background-position: -22340px 0px; }
+.emoji-1F6C4 { background-position: -22360px 0px; }
+.emoji-1F6C5 { background-position: -22380px 0px; }
+.emoji-1F6C6 { background-position: -22400px 0px; }
+.emoji-1F6C7 { background-position: -22420px 0px; }
+.emoji-1F6C8 { background-position: -22440px 0px; }
+.emoji-1F6C9 { background-position: -22460px 0px; }
+.emoji-1F6CA { background-position: -22480px 0px; }
+.emoji-1F6CB { background-position: -22500px 0px; }
+.emoji-1F6CC { background-position: -22520px 0px; }
+.emoji-1F6CD { background-position: -22540px 0px; }
+.emoji-1F6CE { background-position: -22560px 0px; }
+.emoji-1F6CF { background-position: -22580px 0px; }
+.emoji-1F6E0 { background-position: -22600px 0px; }
+.emoji-1F6E1 { background-position: -22620px 0px; }
+.emoji-1F6E2 { background-position: -22640px 0px; }
+.emoji-1F6E3 { background-position: -22660px 0px; }
+.emoji-1F6E4 { background-position: -22680px 0px; }
+.emoji-1F6E5 { background-position: -22700px 0px; }
+.emoji-1F6E6 { background-position: -22720px 0px; }
+.emoji-1F6E7 { background-position: -22740px 0px; }
+.emoji-1F6E8 { background-position: -22760px 0px; }
+.emoji-1F6E9 { background-position: -22780px 0px; }
+.emoji-1F6EA { background-position: -22800px 0px; }
+.emoji-1F6EB { background-position: -22820px 0px; }
+.emoji-1F6EC { background-position: -22840px 0px; }
+.emoji-1F6F0 { background-position: -22860px 0px; }
+.emoji-1F6F1 { background-position: -22880px 0px; }
+.emoji-1F6F2 { background-position: -22900px 0px; }
+.emoji-1F6F3 { background-position: -22920px 0px; }
+.emoji-203C { background-position: -22940px 0px; }
+.emoji-2049 { background-position: -22960px 0px; }
+.emoji-2122 { background-position: -22980px 0px; }
+.emoji-2139 { background-position: -23000px 0px; }
+.emoji-2194 { background-position: -23020px 0px; }
+.emoji-2195 { background-position: -23040px 0px; }
+.emoji-2196 { background-position: -23060px 0px; }
+.emoji-2197 { background-position: -23080px 0px; }
+.emoji-2198 { background-position: -23100px 0px; }
+.emoji-2199 { background-position: -23120px 0px; }
+.emoji-21A9 { background-position: -23140px 0px; }
+.emoji-21AA { background-position: -23160px 0px; }
+.emoji-231A { background-position: -23180px 0px; }
+.emoji-231B { background-position: -23200px 0px; }
+.emoji-23E9 { background-position: -23220px 0px; }
+.emoji-23EA { background-position: -23240px 0px; }
+.emoji-23EB { background-position: -23260px 0px; }
+.emoji-23EC { background-position: -23280px 0px; }
+.emoji-23F0 { background-position: -23300px 0px; }
+.emoji-23F3 { background-position: -23320px 0px; }
+.emoji-24C2 { background-position: -23340px 0px; }
+.emoji-25AA { background-position: -23360px 0px; }
+.emoji-25AB { background-position: -23380px 0px; }
+.emoji-25B6 { background-position: -23400px 0px; }
+.emoji-25C0 { background-position: -23420px 0px; }
+.emoji-25FB { background-position: -23440px 0px; }
+.emoji-25FC { background-position: -23460px 0px; }
+.emoji-25FD { background-position: -23480px 0px; }
+.emoji-25FE { background-position: -23500px 0px; }
+.emoji-2600 { background-position: -23520px 0px; }
+.emoji-2601 { background-position: -23540px 0px; }
+.emoji-260E { background-position: -23560px 0px; }
+.emoji-2611 { background-position: -23580px 0px; }
+.emoji-2614 { background-position: -23600px 0px; }
+.emoji-2615 { background-position: -23620px 0px; }
+.emoji-261D { background-position: -23640px 0px; }
+.emoji-263A { background-position: -23660px 0px; }
+.emoji-2648 { background-position: -23680px 0px; }
+.emoji-2649 { background-position: -23700px 0px; }
+.emoji-264A { background-position: -23720px 0px; }
+.emoji-264B { background-position: -23740px 0px; }
+.emoji-264C { background-position: -23760px 0px; }
+.emoji-264D { background-position: -23780px 0px; }
+.emoji-264E { background-position: -23800px 0px; }
+.emoji-264F { background-position: -23820px 0px; }
+.emoji-2650 { background-position: -23840px 0px; }
+.emoji-2651 { background-position: -23860px 0px; }
+.emoji-2652 { background-position: -23880px 0px; }
+.emoji-2653 { background-position: -23900px 0px; }
+.emoji-2660 { background-position: -23920px 0px; }
+.emoji-2663 { background-position: -23940px 0px; }
+.emoji-2665 { background-position: -23960px 0px; }
+.emoji-2666 { background-position: -23980px 0px; }
+.emoji-2668 { background-position: -24000px 0px; }
+.emoji-267B { background-position: -24020px 0px; }
+.emoji-267F { background-position: -24040px 0px; }
+.emoji-2693 { background-position: -24060px 0px; }
+.emoji-26A0 { background-position: -24080px 0px; }
+.emoji-26A1 { background-position: -24100px 0px; }
+.emoji-26AA { background-position: -24120px 0px; }
+.emoji-26AB { background-position: -24140px 0px; }
+.emoji-26BD { background-position: -24160px 0px; }
+.emoji-26BE { background-position: -24180px 0px; }
+.emoji-26C4 { background-position: -24200px 0px; }
+.emoji-26C5 { background-position: -24220px 0px; }
+.emoji-26CE { background-position: -24240px 0px; }
+.emoji-26D4 { background-position: -24260px 0px; }
+.emoji-26EA { background-position: -24280px 0px; }
+.emoji-26F2 { background-position: -24300px 0px; }
+.emoji-26F3 { background-position: -24320px 0px; }
+.emoji-26F5 { background-position: -24340px 0px; }
+.emoji-26FA { background-position: -24360px 0px; }
+.emoji-26FD { background-position: -24380px 0px; }
+.emoji-2702 { background-position: -24400px 0px; }
+.emoji-2705 { background-position: -24420px 0px; }
+.emoji-2708 { background-position: -24440px 0px; }
+.emoji-2709 { background-position: -24460px 0px; }
+.emoji-270A { background-position: -24480px 0px; }
+.emoji-270B { background-position: -24500px 0px; }
+.emoji-270C { background-position: -24520px 0px; }
+.emoji-270F { background-position: -24540px 0px; }
+.emoji-2712 { background-position: -24560px 0px; }
+.emoji-2714 { background-position: -24580px 0px; }
+.emoji-2716 { background-position: -24600px 0px; }
+.emoji-2728 { background-position: -24620px 0px; }
+.emoji-2733 { background-position: -24640px 0px; }
+.emoji-2734 { background-position: -24660px 0px; }
+.emoji-2744 { background-position: -24680px 0px; }
+.emoji-2747 { background-position: -24700px 0px; }
+.emoji-274C { background-position: -24720px 0px; }
+.emoji-274E { background-position: -24740px 0px; }
+.emoji-2753 { background-position: -24760px 0px; }
+.emoji-2754 { background-position: -24780px 0px; }
+.emoji-2755 { background-position: -24800px 0px; }
+.emoji-2757 { background-position: -24820px 0px; }
+.emoji-2764 { background-position: -24840px 0px; }
+.emoji-2795 { background-position: -24860px 0px; }
+.emoji-2796 { background-position: -24880px 0px; }
+.emoji-2797 { background-position: -24900px 0px; }
+.emoji-27A1 { background-position: -24920px 0px; }
+.emoji-27B0 { background-position: -24940px 0px; }
+.emoji-27BF { background-position: -24960px 0px; }
+.emoji-2934 { background-position: -24980px 0px; }
+.emoji-2935 { background-position: -25000px 0px; }
+.emoji-2B05 { background-position: -25020px 0px; }
+.emoji-2B06 { background-position: -25040px 0px; }
+.emoji-2B07 { background-position: -25060px 0px; }
+.emoji-2B1B { background-position: -25080px 0px; }
+.emoji-2B1C { background-position: -25100px 0px; }
+.emoji-2B50 { background-position: -25120px 0px; }
+.emoji-2B55 { background-position: -25140px 0px; }
+.emoji-3030 { background-position: -25160px 0px; }
+.emoji-303D { background-position: -25180px 0px; }
+.emoji-3297 { background-position: -25200px 0px; }
+.emoji-3299 { background-position: -25220px 0px; }
\ No newline at end of file
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 07a38a19fad6b465d3082c6fa9cdaeae48a06704..263993f59a5510eea684ea9ed201d09524fd0930 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -1,8 +1,3 @@
-.new-group-member-holder {
-  margin-top: 50px;
-  padding-top: 20px;
-}
-
 .member-search-form {
   float: left;
 }
@@ -15,4 +10,4 @@
   .form-control {
     height: 42px;
   }
-}
\ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 3a08ee70bc7b523e531b00d699fba8f029e838c9..9da273a0b6b399a664c35eda53d3efcce6c94581 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -24,20 +24,6 @@
   }
 }
 
-.issuable-context-title {
-  margin-bottom: 5px;
-
-  .avatar {
-    margin-left: 0;
-  }
-
-  label {
-    color: $gl-gray;
-    font-weight: normal;
-    margin-right: 4px;
-  }
-}
-
 .project-issuable-filter {
   .controls {
     float: right;
@@ -50,33 +36,11 @@
 }
 
 .issuable-details {
-  .page-title {
-    margin-top: -15px;
-    padding: 10px 0;
-    margin-bottom: 0;
-    color: #5c5d5e;
-    font-size: 16px;
-
-    .author {
-      color: #5c5d5e;
-    }
+  section {
+    border-right: 1px solid $border-white-light;
 
-    .issue-id {
-      color: #5c5d5e;
-    }
-  }
-
-  .issue-title {
-    margin: 0;
-    font-size: 23px;
-    color: #313236;
-  }
-
-  .description {
-    margin-top: 6px;
-
-    p:last-child {
-      margin-bottom: 0;
+    .issuable-discussion {
+      margin-right: 1px;
     }
   }
 }
@@ -89,83 +53,65 @@
   }
 }
 
-.cross-project-reference {
-  text-align: center;
-  width: 100%;
-
-  .slead {
-    padding: 5px;
-  }
-
-  span, button {
-    background-color: $background-color;
+.issuable-show-labels {
+  a {
+    margin-right: 5px;
+    margin-bottom: 5px;
+    display: inline-block;
+    .color-label {
+      padding: 6px 10px;
+    }
   }
 }
 
-.awards {
-  @include clearfix;
-  line-height: 34px;
-  margin: 2px 0;
-
-  .award {
-    @include border-radius(5px);
+.issuable-sidebar {
+  .block {
+    @include clearfix;
+    padding:  $gl-padding 0;
+    border-bottom: 1px solid #F0F0F0;
 
-    border: 1px solid;
-    padding: 0px 10px;
-    float: left;
-    margin: 0 5px;
-    border-color: $border-color;
-    cursor: pointer;
+    &:last-child {
+      border: none;
+    }
+  }
 
-    &.active {
-      border-color: $border-gray-light;
-      background-color: $gray-light;
+  .title {
+    color: $gl-text-color;
+    margin-bottom: 8px;
 
-      .counter {
-        font-weight: bold;
-      }
+    .avatar {
+      margin-left: 0;
     }
 
-    .icon {
-      float: left;
-      margin-right: 10px;
+    label {
+      font-weight: normal;
+      margin-right: 4px;
     }
 
-    .counter {
-      float: left;
+    .edit-link {
+      color: $gl-gray;
     }
   }
 
-  .awards-controls {
-    margin-left: 10px;
-    float: left;
+  .cross-project-reference {
+    font-weight: bold;
+    color: $gl-link-color;
 
-    .add-award {
-      font-size: 24px;
-      color: $gl-gray;
-      position: relative;
-      top: 2px;
-
-      &:hover,
-      &:link {
-        text-decoration: none;
-      }
+    button {
+      float: right;
     }
+  }
 
-    .awards-menu {
-      padding: $gl-padding;
-      min-width: 214px;
+  .selectbox {
+    display: none
+  }
 
-      > li {
-        margin: 5px;
-      }
-    }
+  .btn-clipboard {
+    color: $gl-gray;
   }
 
-  .awards-menu{
-    li {
-      float: left;
-      margin: 3px;
-    }
+  .participants .avatar {
+    margin-top: 6px;
+    margin-right: 2px;
   }
 }
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 41c069f0ad375eeb49d38675ebe53676580cf976..a02a3a72e79fc54bd8f1fa865e79c65823f29064 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -56,21 +56,30 @@
   }
 }
 
-.issue-show-labels {
-  a {
-    margin-right: 5px;
-    margin-bottom: 5px;
-    display: inline-block;
-    .color-label {
-      padding: 6px 10px;
-    }
-  }
-}
-
 form.edit-issue {
   margin: 0;
 }
 
+.merge-requests-title {
+  font-size: 16px;
+  font-weight: 600;
+}
+
+.merge-request-id {
+  display: inline-block;
+  width: 3em;
+}
+
+.merge-request-info {
+  padding-left: 5px;
+}
+
+.merge-request-status {
+  color: $gl-gray;
+  font-size: 15px;
+  font-weight: bold;
+}
+
 .merge-request,
 .issue {
   &.today {
@@ -132,11 +141,6 @@ form.edit-issue {
   }
 }
 
-.issue-closed-by-widget {
-  padding: 16px 0;
-  margin: 0px;
-}
-
 .issue-form .select2-container {
   width: 250px !important;
 }
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 83b866c3a6466f18ffde7359d517774d80a7f373..f9c6f1b39f94324da91cce15100dc140aadbb0e8 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -19,6 +19,7 @@
     h1:first-child {
       font-weight: normal;
       margin-bottom: 30px;
+      margin-top: 0;
     }
 
     img {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 08e4bcdf529ad7354c21cdc5e1252dcac3187cef..82effde0bf3b49e8bb8aed35f1c2387292dfc6ea 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -4,7 +4,6 @@
  */
 .mr-state-widget {
   background: #F7F8FA;
-  margin-bottom: 20px;
   color: $gl-gray;
   border: 1px solid #dce0e6;
   @include border-radius(2px);
@@ -19,6 +18,7 @@
   .accept-merge-holder {
     .accept-action {
       display: inline-block;
+      float: left;
 
       .accept_merge_request {
         &.ci-pending,
@@ -37,14 +37,15 @@
 
     .accept-control {
       display: inline-block;
+      float: left;
       margin: 0;
       margin-left: 20px;
       padding: 5px;
+      padding-top: 12px;
       line-height: 20px;
 
       &.right {
         float: right;
-        padding-top: 12px;
         a {
           color: $gl-gray;
         }
@@ -82,12 +83,16 @@
     &.ci-error {
       color: $gl-danger;
     }
+
+    a.monospace {
+      color: inherit;
+    }
   }
 
   .mr-widget-body,
   .ci_widget,
   .mr-widget-footer {
-    padding: 15px;
+    padding: $gl-padding;
   }
 
   .normal {
@@ -116,28 +121,6 @@
   }
 }
 
-.merge-request .merge-request-tabs {
-  @include nav-menu;
-  margin: -$gl-padding;
-  padding: $gl-padding;
-  text-align: center;
-  margin-bottom: 1px;
-}
-
-// Mobile
-@media (max-width: 480px) {
-  .merge-request .merge-request-tabs {
-    margin: 0;
-    padding: 0;
-
-    li {
-      a {
-        padding: 0;
-      }
-    }
-  }
-}
-
 .mr_source_commit,
 .mr_target_commit {
   .commit {
@@ -155,7 +138,7 @@
   font-family: $monospace_font;
   font-weight: bold;
   overflow: hidden;
-  font-size: 14px;
+  font-size: 90%;
   margin: 0 3px;
 }
 
@@ -192,27 +175,12 @@
   line-height: 1.1;
 }
 
-.merge-request-form-info {
-  padding-top: 15px;
-}
-
 // hide mr close link for inline diff comment form
 .diff-file .close-mr-link,
 .diff-file .reopen-mr-link {
   display: none;
 }
 
-.merge-request-show-labels {
-  a {
-    margin-right: 5px;
-    margin-bottom: 5px;
-    display: inline-block;
-    .color-label {
-      padding: 6px 10px;
-    }
-  }
-}
-
 .merge-request-form .select2-container {
   width: 250px !important;
 }
@@ -223,7 +191,7 @@
   .btn-clipboard {
     @extend .pull-right;
 
-    margin-right: 18px;
+    margin-right: 20px;
     margin-top: 5px;
     position: absolute;
     right: 0;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 268fc995aa72a2d472afaa85077f478355bbd20e..d86259f93fbcfe672cf0fab9d84d03ad196a21fe 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -7,6 +7,7 @@
 }
 .reply-btn {
   @extend .btn-primary;
+  margin: 10px $gl-padding;
 }
 .diff-file .diff-content {
   tr.line_holder:hover {
@@ -38,9 +39,8 @@
 }
 
 .new_note, .edit_note {
-  .buttons {
-    margin-top: 8px;
-    margin-bottom: 3px;
+  .note-form-actions {
+    margin-top: $gl-padding;
   }
 
   .note-preview-holder {
@@ -75,17 +75,15 @@
 
 .common-note-form {
   margin: 0;
-  background: #F7F8FA;
+  background: #fff;
   padding: $gl-padding;
   margin-left: -$gl-padding;
   margin-right: -$gl-padding;
-  border-right: 1px solid #ECEEF1;
-  border-top: 1px solid #ECEEF1;
   margin-bottom: -$gl-padding;
 }
 
 .note-form-actions {
-  background: #F9F9F9;
+  background: #fff;
 
   .note-form-option {
     margin-top: 8px;
@@ -150,7 +148,6 @@
 
   .discussion-reply-holder {
     background: $background-color;
-    padding: 10px 15px;
     border-top: 1px solid $border-color;
   }
 }
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 1980fe0d4580e4f5d39bc87a6bfe35f2e2f79623..72b0ed29a698b1d2e95b79c8c654f61784a2165f 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -109,13 +109,9 @@ ul.notes {
           }
         }
 
-        // Reduce left padding of first task list ul element
-        ul.task-list:first-child {
-          padding-left: 10px;
-
-          // sub-tasks should be padded normally
-          ul {
-            padding-left: 20px;
+        ul.task-list {
+          ul:not(.task-list) {
+            padding-left: 1.3em;
           }
         }
 
@@ -132,7 +128,7 @@ ul.notes {
     }
 
     &:last-child {
-      border-bottom: none;
+      border-bottom: 1px solid $border-color;
     }
   }
 }
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 1d6ca0dfc1302663afdd50099bf7cf78a974aa7b..95fc26a608a70c6f65600965cd153937c8ad6675 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -5,12 +5,6 @@
   }
 }
 
-.btn-build-token {
-  float: left;
-  padding: 6px 20px;
-  margin-right: 12px;
-}
-
 .profile-avatar-form-option {
   hr {
     margin: 10px 0;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index d3b10040022e71ce86097277792e8d728dab8054..cff3edb7ed26c2a5caa0a062001c5af7d705017f 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -5,7 +5,7 @@
     font-weight: normal;
   }
 }
-.no-ssh-key-message {
+.no-ssh-key-message, .project-limit-message {
   background-color: #f28d35;
   margin-bottom: 16px;
 }
@@ -18,10 +18,6 @@
   }
 }
 
-.project-edit-content {
-  padding: 7px;
-}
-
 .project-name-holder {
   .help-inline {
     vertical-align: top;
@@ -30,12 +26,6 @@
 }
 
 .project-home-panel {
-  text-align: center;
-  background: #f7f8fa;
-  margin: -$gl-padding;
-  padding: $gl-padding;
-  padding: 44px 0 17px 0;
-
   .project-identicon-holder {
     margin-bottom: 16px;
 
@@ -90,28 +80,94 @@
   }
 
   .visibility-level-label {
+    @extend .btn;
+    @extend .btn-gray;
+
     color: $gray;
+    cursor: default;
+
     i {
       color: inherit;
     }
   }
 
-  .input-group {
+  .git-clone-holder {
     display: inline-table;
     position: relative;
-    top: 17px;
-    margin-bottom: 44px;
   }
 
   .project-repo-buttons {
     margin-top: 12px;
     margin-bottom: 0px;
 
+    .count-buttons {
+      display: block;
+      margin-bottom: 12px;
+    }
+
     .btn {
       @include btn-gray;
-
+      text-transform: none;
+    }
+    .count-with-arrow {
+      display: inline-block;
+      position: relative;
+      margin-left: 4px;
+
+      .arrow {
+        &:before {
+          content: '';
+          display: inline-block;
+          position: absolute;
+          width: 0;
+          height: 0;
+          border-color: transparent;
+          border-style: solid;
+          top: 50%;
+          left: 0;
+          margin-top: -6px;
+          border-width: 7px 5px 7px 0;
+          border-right-color: #dce0e5;
+        }
+
+        &:after {
+          content: '';
+          position: absolute;
+          width: 0;
+          height: 0;
+          border-color: transparent;
+          border-style: solid;
+          top: 50%;
+          left: 1px;
+          margin-top: -9px;
+          border-width: 10px 7px 10px 0;
+          border-right-color: #FFF;
+        }
+      }
       .count {
+        @include btn-gray;
         display: inline-block;
+        background: white;
+        border-radius: 2px;
+        border-width: 1px;
+        border-style: solid;
+        font-size: 13px;
+        font-weight: 600;
+        line-height: 20px;
+        padding: 11px 16px;
+        letter-spacing: .4px;
+        padding: 10px;
+        text-align: center;
+        vertical-align: middle;
+        touch-action: manipulation;
+        cursor: pointer;
+        background-image: none;
+        white-space: nowrap;
+        margin: 0 11px 0px 4px;
+
+        &:hover {
+          background: #FFF;
+        }
       }
     }
   }
@@ -131,6 +187,13 @@
     margin-right: 45px;
   }
 
+  .clone-options {
+    display: table-cell;
+    a.btn {
+      width: 100%;
+    }
+  }
+
   .form-control {
     cursor: auto;
     @extend .monospace;
@@ -178,6 +241,11 @@
     &:active {
       outline: none;
     }
+
+    &.btn-clipboard {
+      padding-left: 15px;
+      padding-right: 15px;
+    }
   }
 
   .active {
@@ -336,6 +404,38 @@ ul.nav.nav-projects-tabs {
   }
 }
 
+.top-area {
+  border-bottom: 1px solid #EEE;
+  margin: 0 -16px;
+  padding: 0 $gl-padding;
+
+  ul.left-top-menu {
+    display: inline-block;
+    width: 50%;
+    margin-bottom: 0px;
+    border-bottom: none;
+  }
+
+  .projects-search-form {
+    width: 50%;
+    display: inline-block;
+    float: right;
+    padding-top: 7px;
+    text-align: right;
+
+    .btn-green {
+      margin-top: -2px;
+      margin-left: 10px;
+    }
+  }
+
+  @media (max-width: $screen-xs-max) {
+    .projects-search-form {
+      padding-top: 15px;
+    }
+  }
+}
+
 .fork-namespaces {
   .fork-thumbnail {
     text-align: center;
@@ -366,7 +466,7 @@ table.table.protected-branches-list tr.no-border {
 
 .project-stats {
   text-align: center;
-  margin-top: 15px;
+  margin-top: $gl-padding;
   margin-bottom: 0;
   padding-top: 10px;
   padding-bottom: 4px;
@@ -413,11 +513,18 @@ pre.light-well {
 
 .projects-search-form {
   margin: -$gl-padding;
-  background-color: #f8fafc;
   padding: $gl-padding;
   margin-bottom: 0px;
-  border-top: 1px solid #e7e9ed;
-  border-bottom: 1px solid #e7e9ed;
+
+  input {
+    display: inline-block;
+    width: calc(100% - 151px);
+  }
+
+  .btn {
+    display: inline-block;
+    width: 135px;
+  }
 }
 
 .git-empty {
@@ -552,4 +659,4 @@ pre.light-well {
     z-index: 100;
     position: relative;
   }
-}
\ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 242783a7b7e13b46b94423017d4ba60457f86657..1430d01859d9352d472c249a662ad4b9eed0e24c 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -27,56 +27,28 @@
 }
 
 .snippet-holder {
-  .snippet-details {
-    .page-title {
-      margin-top: -15px;
-      padding: 10px 0;
-      margin-bottom: 0;
-      color: #5c5d5e;
-      font-size: 16px;
-
-      .author {
-        color: #5c5d5e;
-      }
-
-      .snippet-id {
-        color: #5c5d5e;
-      }
-    }
-
-    .snippet-title {
-      margin: 0;
-      font-size: 23px;
-      color: #313236;
-    }
-
-    @media (max-width: $screen-md-max) {
-      .new-snippet-link {
-        display: none;
-      }
-    }
-
-    @media (max-width: $screen-sm-max) {
-      .creator,
-      .page-title .btn-close {
-        display: none;
-      }
-    }
-  }
+  margin-bottom: -$gl-padding;
 
   .file-holder {
     border-top: 0;
   }
-}
 
+  .file-actions {
+    .btn-clipboard {
+      @extend .btn;
+    }
+  }
+}
 
 .snippet-box {
   @include border-radius(2px);
 
-  display: inline-block;
-  padding: 10px $gl-padding;
+  display: block;
+  float: left;
+  padding: 0 $gl-padding;
   font-weight: normal;
   margin-right: 10px;
   font-size: $gl-font-size;
   border: 1px solid;
+  line-height: 40px;
 }
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index a7d3b2197f15f82636018d1e00e6ad6b6792256d..4b6ef035673b595f1b835a7444e40abc0cb70ae4 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -35,3 +35,20 @@
     border-color: $gl-warning;
   }
 }
+
+.ci-status-icon-success {
+  @extend .cgreen;
+}
+.ci-status-icon-failed {
+  @extend .cred;
+}
+.ci-status-icon-running,
+.ci-status-icon-pending {
+  // These are standard text color
+}
+.ci-status-icon-canceled,
+.ci-status-icon-disabled,
+.ci-status-icon-not-found,
+.ci-status-icon-skipped {
+  @extend .cgray;
+}
diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss
index 277afa1db9e5cce2fea4627a381afae859a8f6a1..185f3622e6474475d96c0c46ad706b224f86b62f 100644
--- a/app/assets/stylesheets/pages/ui_dev_kit.scss
+++ b/app/assets/stylesheets/pages/ui_dev_kit.scss
@@ -1,9 +1,6 @@
 .gitlab-ui-dev-kit {
   > h2 {
-    font-size: 27px;
-    border-bottom: 1px solid #CCC;
-    color: #666;
-    margin: 30px 0;
+    margin: 35px 0 20px;
     font-weight: bold;
   }
 }
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index dfaeba41cf6603c3d86ca3bfeeb8ac19a9f97310..cdf514197cbe315ebe8fa408acbf796a618ad3a0 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -4,3 +4,8 @@
   margin-right: auto;
   padding-right: 7px;
 }
+
+.wiki-last-edit-by {
+  font-size: 80%;
+  font-weight: normal;
+}
diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb
index 2f4054eaa117e2fd1403905c77b2e894858dcde7..20bc5173f1d0664e5f1d1d0c1b2b8e307fe8ebf2 100644
--- a/app/controllers/abuse_reports_controller.rb
+++ b/app/controllers/abuse_reports_controller.rb
@@ -10,7 +10,7 @@ class AbuseReportsController < ApplicationController
 
     if @abuse_report.save
       if current_application_settings.admin_notification_email.present?
-        AbuseReportMailer.delay.notify(@abuse_report.id)
+        AbuseReportMailer.notify(@abuse_report.id).deliver_later
       end
 
       message = "Thank you for your report. A GitLab administrator will look into it shortly."
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index a9bcfc7456ad394b64ec2c10939b38de44a0c67e..10e736fd362248e9e9f0aa6e3f07fbd11202742a 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -13,6 +13,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
     end
   end
 
+  def reset_runners_token
+    @application_setting.reset_runners_registration_token!
+    flash[:notice] = 'New runners registration token has been generated!'
+    redirect_to admin_runners_path
+  end
+
   private
 
   def set_application_setting
@@ -43,6 +49,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
       :default_branch_protection,
       :signup_enabled,
       :signin_enabled,
+      :require_two_factor_authentication,
+      :two_factor_grace_period,
       :gravatar_enabled,
       :twitter_sharing_enabled,
       :sign_in_text,
@@ -59,6 +67,17 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
       :user_oauth_applications,
       :shared_runners_enabled,
       :max_artifacts_size,
+      :metrics_enabled,
+      :metrics_host,
+      :metrics_port,
+      :metrics_username,
+      :metrics_password,
+      :metrics_pool_size,
+      :metrics_timeout,
+      :metrics_method_call_threshold,
+      :recaptcha_enabled,
+      :recaptcha_site_key,
+      :recaptcha_private_key,
       restricted_visibility_levels: [],
       import_sources: []
     )
diff --git a/app/controllers/admin/builds_controller.rb b/app/controllers/admin/builds_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..83d9684c706a2e69c973ed7817a555f1ff9c3657
--- /dev/null
+++ b/app/controllers/admin/builds_controller.rb
@@ -0,0 +1,23 @@
+class Admin::BuildsController < Admin::ApplicationController
+  def index
+    @scope = params[:scope]
+    @all_builds = Ci::Build
+    @builds = @all_builds.order('created_at DESC')
+    @builds =
+      case @scope
+      when 'all'
+        @builds
+      when 'finished'
+        @builds.finished
+      else
+        @builds.running_or_pending.reverse_order
+      end
+    @builds = @builds.page(params[:page]).per(30)
+  end
+
+  def cancel_all
+    Ci::Build.running_or_pending.each(&:cancel)
+
+    redirect_to admin_builds_path
+  end
+end
diff --git a/app/controllers/admin/identities_controller.rb b/app/controllers/admin/identities_controller.rb
index d28614731f9211cf66fb1ef3af5e76a2c3fe972d..e383fe38ea68352c5ba172a396a978f805a27527 100644
--- a/app/controllers/admin/identities_controller.rb
+++ b/app/controllers/admin/identities_controller.rb
@@ -1,6 +1,21 @@
 class Admin::IdentitiesController < Admin::ApplicationController
   before_action :user
-  before_action :identity, except: :index
+  before_action :identity, except: [:index, :new, :create]
+
+  def new
+    @identity = Identity.new
+  end
+
+  def create
+    @identity = Identity.new(identity_params)
+    @identity.user_id = user.id
+
+    if @identity.save
+      redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully created.'
+    else
+      render :new
+    end
+  end
 
   def index
     @identities = @user.identities
diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb
index 0382402afa6432de55f9c14e4b0a13dd825f5e88..bf98af786158ce693956a442142a899b48d0cdf2 100644
--- a/app/controllers/admin/impersonation_controller.rb
+++ b/app/controllers/admin/impersonation_controller.rb
@@ -5,14 +5,20 @@ class Admin::ImpersonationController < Admin::ApplicationController
   before_action :authorize_impersonator!
 
   def create
-    session[:impersonator_id] = current_user.username
-    session[:impersonator_return_to] = request.env['HTTP_REFERER']
+    if @user.blocked?
+      flash[:alert] = "You cannot impersonate a blocked user"
 
-    warden.set_user(user, scope: 'user')
+      redirect_to admin_user_path(@user)
+    else
+      session[:impersonator_id] = current_user.username
+      session[:impersonator_return_to] = admin_user_path(@user)
+
+      warden.set_user(user, scope: 'user')
 
-    flash[:alert] = "You are impersonating #{user.username}."
+      flash[:alert] = "You are impersonating #{user.username}."
 
-    redirect_to root_path
+      redirect_to root_path
+    end
   end
 
   def destroy
diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d25619d94e0be2f9be72124d2d31d1c183ffbc65
--- /dev/null
+++ b/app/controllers/admin/runner_projects_controller.rb
@@ -0,0 +1,35 @@
+class Admin::RunnerProjectsController < Admin::ApplicationController
+  before_action :project, only: [:create]
+
+  def index
+    @runner_projects = project.runner_projects.all
+    @runner_project = project.runner_projects.new
+  end
+
+  def create
+    @runner = Ci::Runner.find(params[:runner_project][:runner_id])
+
+    if @runner.assign_to(@project, current_user)
+      redirect_to admin_runner_path(@runner)
+    else
+      redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
+    end
+  end
+
+  def destroy
+    rp = Ci::RunnerProject.find(params[:id])
+    runner = rp.runner
+    rp.destroy
+
+    redirect_to admin_runner_path(runner)
+  end
+
+  private
+
+  def project
+    @project = Project.find_with_namespace(
+      [params[:namespace_id], '/', params[:project_id]].join('')
+    )
+    @project || render_404
+  end
+end
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a701d49b844b9523299be96e2486bb0167e0b95e
--- /dev/null
+++ b/app/controllers/admin/runners_controller.rb
@@ -0,0 +1,63 @@
+class Admin::RunnersController < Admin::ApplicationController
+  before_action :runner, except: :index
+
+  def index
+    @runners = Ci::Runner.order('id DESC')
+    @runners = @runners.search(params[:search]) if params[:search].present?
+    @runners = @runners.page(params[:page]).per(30)
+    @active_runners_cnt = Ci::Runner.online.count
+  end
+
+  def show
+    @builds = @runner.builds.order('id DESC').first(30)
+    @projects =
+      if params[:search].present?
+        ::Project.search(params[:search])
+      else
+        Project.all
+      end
+    @projects = @projects.where.not(id: @runner.projects.select(:id)) if @runner.projects.any?
+    @projects = @projects.page(params[:page]).per(30)
+  end
+
+  def update
+    @runner.update_attributes(runner_params)
+
+    respond_to do |format|
+      format.js
+      format.html { redirect_to admin_runner_path(@runner) }
+    end
+  end
+
+  def destroy
+    @runner.destroy
+
+    redirect_to admin_runners_path
+  end
+
+  def resume
+    if @runner.update_attributes(active: true)
+      redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
+    else
+      redirect_to admin_runners_path, alert: 'Runner was not updated.'
+    end
+  end
+
+  def pause
+    if @runner.update_attributes(active: false)
+      redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
+    else
+      redirect_to admin_runners_path, alert: 'Runner was not updated.'
+    end
+  end
+
+  private
+
+  def runner
+    @runner ||= Ci::Runner.find(params[:id])
+  end
+
+  def runner_params
+    params.require(:runner).permit(:token, :description, :tag_list, :active)
+  end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 0d182e8eb04d92fded1152636459a9be917d82c1..d9a37a4d45f8bccd7961e0e731ecd46f3b95eef0 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -10,8 +10,10 @@ class ApplicationController < ActionController::Base
 
   before_action :authenticate_user_from_token!
   before_action :authenticate_user!
+  before_action :validate_user_service_ticket!
   before_action :reject_blocked!
   before_action :check_password_expiration
+  before_action :check_2fa_requirement
   before_action :ldap_security_check
   before_action :default_headers
   before_action :add_gon_variables
@@ -202,12 +204,32 @@ class ApplicationController < ActionController::Base
     end
   end
 
+  def validate_user_service_ticket!
+    return unless signed_in? && session[:service_tickets]
+
+    valid = session[:service_tickets].all? do |provider, ticket|
+      Gitlab::OAuth::Session.valid?(provider, ticket)
+    end
+
+    unless valid
+      session[:service_tickets] = nil
+      sign_out current_user
+      redirect_to new_user_session_path
+    end
+  end
+
   def check_password_expiration
     if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now  && !current_user.ldap_user?
       redirect_to new_profile_password_path and return
     end
   end
 
+  def check_2fa_requirement
+    if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled && !skip_two_factor?
+      redirect_to new_profile_two_factor_auth_path
+    end
+  end
+
   def ldap_security_check
     if current_user && current_user.requires_ldap_check?
       unless Gitlab::LDAP::Access.allowed?(current_user)
@@ -342,6 +364,23 @@ class ApplicationController < ActionController::Base
     current_application_settings.import_sources.include?('git')
   end
 
+  def two_factor_authentication_required?
+    current_application_settings.require_two_factor_authentication
+  end
+
+  def two_factor_grace_period
+    current_application_settings.two_factor_grace_period
+  end
+
+  def two_factor_grace_period_expired?
+    date = current_user.otp_grace_period_started_at
+    date && (date + two_factor_grace_period.hours) < Time.current
+  end
+
+  def skip_two_factor?
+    session[:skip_tfa] && session[:skip_tfa] > Time.current
+  end
+
   def redirect_to_home_page_url?
     # If user is not signed-in and tries to access root_path - redirect him to landing page
     # Don't redirect to the default URL to prevent endless redirections
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index aa0268b8d621800926ed8a83d5079b7fc14d5f26..77c8dafc012eec88854d5efc7bf763b7b4406d2b 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -9,7 +9,7 @@ class AutocompleteController < ApplicationController
     @users = @users.reorder(:name)
     @users = @users.page(params[:page]).per(PER_PAGE)
 
-    unless params[:search].present?
+    if params[:search].blank?
       # Include current user if available to filter by "Me"
       if params[:current_user] && current_user
         @users = [*@users, current_user].uniq
diff --git a/app/controllers/ci/admin/application_controller.rb b/app/controllers/ci/admin/application_controller.rb
deleted file mode 100644
index 4ec2dc9c2cf2322fbd065ad24931c1cf2a65b095..0000000000000000000000000000000000000000
--- a/app/controllers/ci/admin/application_controller.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module Ci
-  module Admin
-    class ApplicationController < Ci::ApplicationController
-      before_action :authenticate_user!
-      before_action :authenticate_admin!
-
-      layout "ci/admin"
-    end
-  end
-end
diff --git a/app/controllers/ci/admin/application_settings_controller.rb b/app/controllers/ci/admin/application_settings_controller.rb
deleted file mode 100644
index 71e253fac6715beee89f0f3e781179903c1aec87..0000000000000000000000000000000000000000
--- a/app/controllers/ci/admin/application_settings_controller.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-module Ci
-  class Admin::ApplicationSettingsController < Ci::Admin::ApplicationController
-    before_action :set_application_setting
-
-    def show
-    end
-
-    def update
-      if @application_setting.update_attributes(application_setting_params)
-        redirect_to ci_admin_application_settings_path,
-          notice: 'Application settings saved successfully'
-      else
-        render :show
-      end
-    end
-
-    private
-
-    def set_application_setting
-      @application_setting = Ci::ApplicationSetting.current
-      @application_setting ||= Ci::ApplicationSetting.create_from_defaults
-    end
-
-    def application_setting_params
-      params.require(:application_setting).permit(
-        :all_broken_builds,
-        :add_pusher,
-      )
-    end
-  end
-end
diff --git a/app/controllers/ci/admin/builds_controller.rb b/app/controllers/ci/admin/builds_controller.rb
deleted file mode 100644
index 38abfdeafbfb17f06b4c6218697f7227de0e18ec..0000000000000000000000000000000000000000
--- a/app/controllers/ci/admin/builds_controller.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-module Ci
-  class Admin::BuildsController < Ci::Admin::ApplicationController
-    def index
-      @scope = params[:scope]
-      @builds = Ci::Build.order('created_at DESC').page(params[:page]).per(30)
-
-      @builds =
-        case @scope
-        when "pending"
-          @builds.pending
-        when "running"
-          @builds.running
-        else
-          @builds
-        end
-    end
-  end
-end
diff --git a/app/controllers/ci/admin/events_controller.rb b/app/controllers/ci/admin/events_controller.rb
deleted file mode 100644
index 5939efff98048c2477568e699bd52ad031d761dc..0000000000000000000000000000000000000000
--- a/app/controllers/ci/admin/events_controller.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module Ci
-  class Admin::EventsController < Ci::Admin::ApplicationController
-    EVENTS_PER_PAGE = 50
-
-    def index
-      @events = Ci::Event.admin.order('created_at DESC').page(params[:page]).per(EVENTS_PER_PAGE)
-    end
-  end
-end
diff --git a/app/controllers/ci/admin/projects_controller.rb b/app/controllers/ci/admin/projects_controller.rb
deleted file mode 100644
index 5bbd0ce739645349b50cf978bfc5e1bdd801871c..0000000000000000000000000000000000000000
--- a/app/controllers/ci/admin/projects_controller.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module Ci
-  class Admin::ProjectsController < Ci::Admin::ApplicationController
-    def index
-      @projects = Ci::Project.ordered_by_last_commit_date.page(params[:page]).per(30)
-    end
-
-    def destroy
-      project.destroy
-
-      redirect_to ci_projects_url
-    end
-
-    protected
-
-    def project
-      @project ||= Ci::Project.find(params[:id])
-    end
-  end
-end
diff --git a/app/controllers/ci/admin/runner_projects_controller.rb b/app/controllers/ci/admin/runner_projects_controller.rb
deleted file mode 100644
index e7de6eb12ca193709128b73ee7c07710788619a5..0000000000000000000000000000000000000000
--- a/app/controllers/ci/admin/runner_projects_controller.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Ci
-  class Admin::RunnerProjectsController < Ci::Admin::ApplicationController
-    layout 'ci/project'
-
-    def index
-      @runner_projects = project.runner_projects.all
-      @runner_project = project.runner_projects.new
-    end
-
-    def create
-      @runner = Ci::Runner.find(params[:runner_project][:runner_id])
-
-      if @runner.assign_to(project, current_user)
-        redirect_to ci_admin_runner_path(@runner)
-      else
-        redirect_to ci_admin_runner_path(@runner), alert: 'Failed adding runner to project'
-      end
-    end
-
-    def destroy
-      rp = Ci::RunnerProject.find(params[:id])
-      runner = rp.runner
-      rp.destroy
-
-      redirect_to ci_admin_runner_path(runner)
-    end
-
-    private
-
-    def project
-      @project ||= Ci::Project.find(params[:project_id])
-    end
-  end
-end
diff --git a/app/controllers/ci/admin/runners_controller.rb b/app/controllers/ci/admin/runners_controller.rb
deleted file mode 100644
index 0cafad27418b043913aba0a843d9b888d07ada12..0000000000000000000000000000000000000000
--- a/app/controllers/ci/admin/runners_controller.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-module Ci
-  class Admin::RunnersController < Ci::Admin::ApplicationController
-    before_action :runner, except: :index
-
-    def index
-      @runners = Ci::Runner.order('id DESC')
-      @runners = @runners.search(params[:search]) if params[:search].present?
-      @runners = @runners.page(params[:page]).per(30)
-      @active_runners_cnt = Ci::Runner.online.count
-    end
-
-    def show
-      @builds = @runner.builds.order('id DESC').first(30)
-      @projects = Ci::Project.all
-      if params[:search].present?
-        @gl_projects = ::Project.search(params[:search])
-        @projects = @projects.where(gitlab_id: @gl_projects.select(:id))
-      end
-      @projects = @projects.where("ci_projects.id NOT IN (?)", @runner.projects.pluck(:id)) if @runner.projects.any?
-      @projects = @projects.joins(:gl_project)
-      @projects = @projects.page(params[:page]).per(30)
-    end
-
-    def update
-      @runner.update_attributes(runner_params)
-
-      respond_to do |format|
-        format.js
-        format.html { redirect_to ci_admin_runner_path(@runner) }
-      end
-    end
-
-    def destroy
-      @runner.destroy
-
-      redirect_to ci_admin_runners_path
-    end
-
-    def resume
-      if @runner.update_attributes(active: true)
-        redirect_to ci_admin_runners_path, notice: 'Runner was successfully updated.'
-      else
-        redirect_to ci_admin_runners_path, alert: 'Runner was not updated.'
-      end
-    end
-
-    def pause
-      if @runner.update_attributes(active: false)
-        redirect_to ci_admin_runners_path, notice: 'Runner was successfully updated.'
-      else
-        redirect_to ci_admin_runners_path, alert: 'Runner was not updated.'
-      end
-    end
-
-    def assign_all
-      Ci::Project.unassigned(@runner).all.each do |project|
-        @runner.assign_to(project, current_user)
-      end
-
-      redirect_to ci_admin_runner_path(@runner), notice: "Runner was assigned to all projects"
-    end
-
-    private
-
-    def runner
-      @runner ||= Ci::Runner.find(params[:id])
-    end
-
-    def runner_params
-      params.require(:runner).permit(:token, :description, :tag_list, :active)
-    end
-  end
-end
diff --git a/app/controllers/ci/application_controller.rb b/app/controllers/ci/application_controller.rb
index 848f2b4e314e1eb4344bef0f99c4d91e15bff45d..c420b59c3a20708d3000b00d24c69c9c5f6263db 100644
--- a/app/controllers/ci/application_controller.rb
+++ b/app/controllers/ci/application_controller.rb
@@ -4,24 +4,16 @@ module Ci
       "app/helpers/ci"
     end
 
-    helper_method :gl_project
-
     private
 
-    def authenticate_token!
-      unless project.valid_token?(params[:token])
-        return head(403)
-      end
-    end
-
     def authorize_access_project!
-      unless can?(current_user, :read_project, gl_project)
+      unless can?(current_user, :read_project, project)
         return page_404
       end
     end
 
     def authorize_manage_builds!
-      unless can?(current_user, :manage_builds, gl_project)
+      unless can?(current_user, :manage_builds, project)
         return page_404
       end
     end
@@ -31,7 +23,7 @@ module Ci
     end
 
     def authorize_manage_project!
-      unless can?(current_user, :admin_project, gl_project)
+      unless can?(current_user, :admin_project, project)
         return page_404
       end
     end
@@ -58,9 +50,5 @@ module Ci
         count: count
       }
     end
-
-    def gl_project
-      ::Project.find(@project.gitlab_id)
-    end
   end
 end
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index a4f6aff49b4556930bcbd6ec3fda964c466d376b..e782a51e7eb93dce8ca605dd2236cd749a272119 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -1,5 +1,5 @@
 module Ci
-  class LintsController < Ci::ApplicationController
+  class LintsController < ApplicationController
     before_action :authenticate_user!
 
     def show
@@ -19,8 +19,10 @@ module Ci
       @error = e.message
       @status = false
     rescue
-      @error = "Undefined error"
+      @error = 'Undefined error'
       @status = false
+    ensure
+      render :show
     end
   end
 end
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index 8406399fb60a3cb98d7d1279b3f9feb19af31512..3004c2d27f05b9c7fa5d1fa3b23338e3eeab0748 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -3,13 +3,12 @@ module Ci
     before_action :project, except: [:index]
     before_action :authenticate_user!, except: [:index, :build, :badge]
     before_action :authorize_access_project!, except: [:index, :badge]
-    before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
     before_action :no_cache, only: [:badge]
     protect_from_forgery
 
     def show
       # Temporary compatibility with CI badges pointing to CI project page
-      redirect_to namespace_project_path(project.gl_project.namespace, project.gl_project)
+      redirect_to namespace_project_path(project.namespace, project)
     end
 
     # Project status badge
@@ -20,16 +19,10 @@ module Ci
       send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml"
     end
 
-    def toggle_shared_runners
-      project.toggle!(:shared_runners_enabled)
-
-      redirect_to namespace_project_runners_path(project.gl_project.namespace, project.gl_project)
-    end
-
     protected
 
     def project
-      @project ||= Ci::Project.find(params[:id])
+      @project ||= Project.find_by(ci_id: params[:id].to_i)
     end
 
     def no_cache
diff --git a/app/controllers/ci/runner_projects_controller.rb b/app/controllers/ci/runner_projects_controller.rb
deleted file mode 100644
index 9d555313369a51c282d72624158d6a4f91fa4d9b..0000000000000000000000000000000000000000
--- a/app/controllers/ci/runner_projects_controller.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Ci
-  class RunnerProjectsController < Ci::ApplicationController
-    before_action :authenticate_user!
-    before_action :project
-    before_action :authorize_manage_project!
-
-    def create
-      @runner = Ci::Runner.find(params[:runner_project][:runner_id])
-
-      return head(403) unless current_user.ci_authorized_runners.include?(@runner)
-
-      path = runners_path(@project.gl_project)
-
-      if @runner.assign_to(project, current_user)
-        redirect_to path
-      else
-        redirect_to path, alert: 'Failed adding runner to project'
-      end
-    end
-
-    def destroy
-      runner_project = project.runner_projects.find(params[:id])
-      runner_project.destroy
-
-      redirect_to runners_path(@project.gl_project)
-    end
-
-    private
-
-    def project
-      @project ||= Ci::Project.find(params[:project_id])
-    end
-  end
-end
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
new file mode 100644
index 0000000000000000000000000000000000000000..62127a090817e01355ec314399db321c0d378a56
--- /dev/null
+++ b/app/controllers/concerns/creates_commit.rb
@@ -0,0 +1,103 @@
+module CreatesCommit
+  extend ActiveSupport::Concern
+
+  def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
+    set_commit_variables
+
+    commit_params = @commit_params.merge(
+      source_project: @project,
+      source_branch: @ref,
+      target_branch: @target_branch
+    )
+
+    result = service.new(@tree_edit_project, current_user, commit_params).execute
+
+    if result[:status] == :success
+      flash[:notice] = success_notice || "Your changes have been successfully committed."
+
+      if create_merge_request?
+        success_path = new_merge_request_path
+        target = different_project? ? "project" : "branch"
+        flash[:notice] << " You can now submit a merge request to get this change into the original #{target}."
+      end
+
+      respond_to do |format|
+        format.html { redirect_to success_path }
+        format.json { render json: { message: "success", filePath: success_path } }
+      end
+    else
+      flash[:alert] = result[:message]
+      respond_to do |format|
+        format.html do
+          if failure_view
+            render failure_view
+          else
+            redirect_to failure_path
+          end
+        end
+        format.json { render json: { message: "failed", filePath: failure_path } }
+      end
+    end
+  end
+
+  def authorize_edit_tree!
+    return if can?(current_user, :push_code, project)
+    return if current_user && current_user.already_forked?(project)
+
+    access_denied!
+  end
+
+  private
+
+  def new_merge_request_path
+    new_namespace_project_merge_request_path(
+      @mr_source_project.namespace,
+      @mr_source_project,
+      merge_request: {
+        source_project_id: @mr_source_project.id,
+        target_project_id: @mr_target_project.id,
+        source_branch: @mr_source_branch,
+        target_branch: @mr_target_branch
+      }
+    )
+  end
+
+  def different_project?
+    @mr_source_project != @mr_target_project
+  end
+
+  def different_branch?
+    @mr_source_branch != @mr_target_branch || different_project?
+  end
+
+  def create_merge_request?
+    params[:create_merge_request].present? && different_branch?
+  end
+
+  def set_commit_variables
+    @mr_source_branch = @target_branch
+
+    if can?(current_user, :push_code, @project)
+      # Edit file in this project
+      @tree_edit_project = @project
+      @mr_source_project = @project
+
+      if @project.forked?
+        # Merge request from this project to fork origin
+        @mr_target_project = @project.forked_from_project
+        @mr_target_branch = @mr_target_project.repository.root_ref
+      else
+        # Merge request to this project
+        @mr_target_project = @project
+        @mr_target_branch = @ref
+      end
+    else
+      # Edit file in fork
+      @tree_edit_project = current_user.fork_of(@project)
+      # Merge request from fork to this project
+      @mr_source_project = @tree_edit_project
+      @mr_target_project = @project
+      @mr_target_branch = @mr_target_project.repository.root_ref
+    end
+  end
+end
diff --git a/app/controllers/concerns/creates_merge_request_for_commit.rb b/app/controllers/concerns/creates_merge_request_for_commit.rb
deleted file mode 100644
index c75278221585a95a18e591f63c1e7839727ace7a..0000000000000000000000000000000000000000
--- a/app/controllers/concerns/creates_merge_request_for_commit.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module CreatesMergeRequestForCommit
-  extend ActiveSupport::Concern
-
-  def new_merge_request_path
-    if @project.forked?
-      target_project = @project.forked_from_project || @project
-      target_branch = target_project.repository.root_ref
-    else
-      target_project = @project
-      target_branch = @ref
-    end
-
-    new_namespace_project_merge_request_path(
-      @project.namespace,
-      @project,
-      merge_request: {
-        source_project_id: @project.id,
-        target_project_id: target_project.id,
-        source_branch: @new_branch,
-        target_branch: target_branch
-      }
-    )
-  end
-
-  def create_merge_request?
-    params[:create_merge_request] && @new_branch != @ref
-  end
-end
diff --git a/app/controllers/concerns/global_milestones.rb b/app/controllers/concerns/global_milestones.rb
index b428249acd3eed58de5dbdad1fb8a033ac689d81..3e4c0e63601d4296333a7327c7973ed4af5b661a 100644
--- a/app/controllers/concerns/global_milestones.rb
+++ b/app/controllers/concerns/global_milestones.rb
@@ -2,8 +2,10 @@ module GlobalMilestones
   extend ActiveSupport::Concern
 
   def milestones
+    epoch = DateTime.parse('1970-01-01')
     @milestones = MilestonesFinder.new.execute(@projects, params)
     @milestones = GlobalMilestone.build_collection(@milestones)
+    @milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
     @milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
   end
 
diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb
index f4354c6d8cad9665e6cbb4cd69a6191338202915..b3594d82530b09d06bfb97e2ec7189b42bf98313 100644
--- a/app/controllers/dashboard/snippets_controller.rb
+++ b/app/controllers/dashboard/snippets_controller.rb
@@ -1,6 +1,7 @@
 class Dashboard::SnippetsController < Dashboard::ApplicationController
   def index
-    @snippets = SnippetsFinder.new.execute(current_user,
+    @snippets = SnippetsFinder.new.execute(
+      current_user,
       filter: :by_user,
       user: current_user,
       scope: params[:scope]
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 10233222ee1b8f76c6a78fe1a29c05cfbf33122a..0c2a350bc39ef24b1ab809d4ee5d75d4d6c4cfc1 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -46,7 +46,7 @@ class Groups::MilestonesController < Groups::ApplicationController
   end
 
   def milestone_path(title)
-    group_milestone_path(@group, title.parameterize, title: title)
+    group_milestone_path(@group, title.to_slug.to_s, title: title)
   end
 
   def projects
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index f809fa7500a09fd37a547238198987e80501f5ea..4cad98b8e98bdd7044ed442ea0622418e14f016a 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -1,6 +1,6 @@
 class OmniauthCallbacksController < Devise::OmniauthCallbacksController
 
-  protect_from_forgery except: [:kerberos, :saml]
+  protect_from_forgery except: [:kerberos, :saml, :cas3]
 
   Gitlab.config.omniauth.providers.each do |provider|
     define_method provider['name'] do
@@ -42,6 +42,14 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
     render 'errors/omniauth_error', layout: "errors", status: 422
   end
 
+  def cas3
+    ticket = params['ticket']
+    if ticket
+      handle_service_ticket oauth['provider'], ticket
+    end
+    handle_omniauth
+  end
+
   private
 
   def handle_omniauth
@@ -84,6 +92,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
     redirect_to new_user_session_path
   end
 
+  def handle_service_ticket provider, ticket
+    Gitlab::OAuth::Session.create provider, ticket
+    session[:service_tickets] ||= {}
+    session[:service_tickets][provider] = ticket
+  end
+
   def oauth
     @oauth ||= request.env['omniauth.auth']
   end
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index 2025158d06598cc86e42af1a232fb4ad2ff823cd..f74daff3bd03f487ee0f446f499ba7186ce79291 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -40,7 +40,9 @@ class PasswordsController < Devise::PasswordsController
   def throttle_reset
     return unless resource && resource.recently_sent_password_reset?
 
-    redirect_to new_password_path(resource_name),
-      alert: I18n.t('devise.passwords.recently_reset')
+    # Throttle reset attempts, but return a normal message to
+    # avoid user enumeration attack.
+    redirect_to new_user_session_path,
+      notice: I18n.t('devise.passwords.send_paranoid_instructions')
   end
 end
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index e6b99be37fb7fab9bcf2190321a3df0e446b050f..6e91d9b4ad96791adb21a9288b2ac54857d63688 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -1,8 +1,22 @@
 class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
+  skip_before_action :check_2fa_requirement
+
   def new
     unless current_user.otp_secret
       current_user.otp_secret = User.generate_otp_secret(32)
-      current_user.save!
+    end
+
+    unless current_user.otp_grace_period_started_at && two_factor_grace_period
+      current_user.otp_grace_period_started_at = Time.current
+    end
+
+    current_user.save! if current_user.changed?
+
+    if two_factor_grace_period_expired?
+      flash.now[:alert] = 'You must configure Two-Factor Authentication in your account.'
+    else
+      grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
+      flash.now[:alert] = "You must configure Two-Factor Authentication in your account until #{l(grace_period_deadline)}."
     end
 
     @qr_code = build_qr_code
@@ -34,6 +48,15 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
     redirect_to profile_account_path
   end
 
+  def skip
+    if two_factor_grace_period_expired?
+      redirect_to new_profile_two_factor_auth_path, alert: 'Cannot skip two factor authentication setup'
+    else
+      session[:skip_tfa] = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
+      redirect_to root_path
+    end
+  end
+
   private
 
   def build_qr_code
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 8da7b4d50ea669faf70c3027b2a6d8c01cc593b6..28803164fcfb29d079f76598b4b75b5069fb65b7 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -70,6 +70,7 @@ class ProfilesController < Profiles::ApplicationController
       :email,
       :hide_no_password,
       :hide_no_ssh_key,
+      :hide_project_limit,
       :linkedin,
       :location,
       :name,
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index d3f926b62bcdaabdfaa9ddb37864c56aeba78a44..dd32d509191ecb1359be717b560a773105504e55 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -21,18 +21,14 @@ class Projects::ApplicationController < ApplicationController
     unless @repository.branch_names.include?(@ref)
       redirect_to(
         namespace_project_tree_path(@project.namespace, @project, @ref),
-        notice: "This action is not allowed unless you are on top of a branch"
+        notice: "This action is not allowed unless you are on a branch"
       )
     end
   end
 
   private
 
-  def ci_enabled
+  def builds_enabled
     return render_404 unless @project.builds_enabled?
   end
-
-  def ci_project
-    @ci_project ||= @project.ensure_gitlab_ci_project
-  end
 end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 31a33bfd237ca896332cd538f8424126f6b4d89a..c56a3497bb2d096cb00231fa979148c0c22c8037 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -1,7 +1,7 @@
 # Controller for viewing a file's blame
 class Projects::BlobController < Projects::ApplicationController
   include ExtractsPath
-  include CreatesMergeRequestForCommit
+  include CreatesCommit
   include ActionView::Helpers::SanitizeHelper
 
   # Raised when given an invalid file path
@@ -9,21 +9,21 @@ class Projects::BlobController < Projects::ApplicationController
 
   before_action :require_non_empty_project, except: [:new, :create]
   before_action :authorize_download_code!
-  before_action :authorize_push_code!, only: [:destroy, :create]
+  before_action :authorize_edit_tree!, only: [:new, :create, :edit, :update, :destroy]
   before_action :assign_blob_vars
   before_action :commit, except: [:new, :create]
   before_action :blob, except: [:new, :create]
   before_action :from_merge_request, only: [:edit, :update]
   before_action :require_branch_head, only: [:edit, :update]
   before_action :editor_variables, except: [:show, :preview, :diff]
-  before_action :after_edit_path, only: [:edit, :update]
 
   def new
     commit unless @repository.empty?
   end
 
   def create
-    create_commit(Files::CreateService, success_path: after_create_path,
+    create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
+                                        success_path: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)),
                                         failure_view: :new,
                                         failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
   end
@@ -36,6 +36,14 @@ class Projects::BlobController < Projects::ApplicationController
   end
 
   def update
+    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) +
+          "#file-path-#{hexdigest(@path)}"
+      else
+        namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
+      end
+
     create_commit(Files::UpdateService, success_path: after_edit_path,
                                         failure_view: :edit,
                                         failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
@@ -50,15 +58,10 @@ class Projects::BlobController < Projects::ApplicationController
   end
 
   def destroy
-    result = Files::DeleteService.new(@project, current_user, @commit_params).execute
-
-    if result[:status] == :success
-      flash[:notice] = "Your changes have been successfully committed"
-      redirect_to after_destroy_path
-    else
-      flash[:alert] = result[:message]
-      render :show
-    end
+    create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
+                                        success_path: namespace_project_tree_path(@project.namespace, @project, @target_branch),
+                                        failure_view: :show,
+                                        failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
   end
 
   def diff
@@ -108,66 +111,13 @@ class Projects::BlobController < Projects::ApplicationController
     render_404
   end
 
-  def create_commit(service, success_path:, failure_view:, failure_path:)
-    result = service.new(@project, current_user, @commit_params).execute
-
-    if result[:status] == :success
-      flash[:notice] = "Your changes have been successfully committed"
-      respond_to do |format|
-        format.html { redirect_to success_path }
-        format.json { render json: { message: "success", filePath: success_path } }
-      end
-    else
-      flash[:alert] = result[:message]
-      respond_to do |format|
-        format.html { render failure_view }
-        format.json { render json: { message: "failed", filePath: failure_path } }
-      end
-    end
-  end
-
-  def after_create_path
-    @after_create_path ||=
-      if create_merge_request?
-        new_merge_request_path
-      else
-        namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @file_path))
-      end
-  end
-
-  def after_edit_path
-    @after_edit_path ||=
-      if create_merge_request?
-        new_merge_request_path
-      elsif from_merge_request && @new_branch == @ref
-        diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
-          "#file-path-#{hexdigest(@path)}"
-      else
-        namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @path))
-      end
-  end
-
-  def after_destroy_path
-    @after_destroy_path ||=
-      if create_merge_request?
-        new_merge_request_path
-      else
-        namespace_project_tree_path(@project.namespace, @project, @new_branch)
-      end
-  end
-
   def from_merge_request
     # If blob edit was initiated from merge request page
     @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
   end
 
-  def sanitized_new_branch_name
-    @new_branch ||= sanitize(strip_tags(params[:new_branch]))
-  end
-
   def editor_variables
-    @current_branch = @ref
-    @new_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref
+    @target_branch = params[:target_branch]
 
     @file_path =
       if action_name.to_s == 'create'
@@ -186,8 +136,6 @@ class Projects::BlobController < Projects::ApplicationController
 
     @commit_params = {
       file_path: @file_path,
-      current_branch: @current_branch,
-      target_branch: @new_branch,
       commit_message: params[:commit_message],
       file_content: params[:content],
       file_content_encoding: params[:encoding]
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 3ac0a75fa709936ccdb384add23bcbb90de5125b..3c2849a760106202a63ac49a3a106fdf88c712c3 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -3,7 +3,7 @@ class Projects::BranchesController < Projects::ApplicationController
   # Authorize
   before_action :require_non_empty_project
   before_action :authorize_download_code!
-  before_action :authorize_push_code!, only: [:create, :destroy]
+  before_action :authorize_push_code!, only: [:new, :create, :destroy]
 
   def index
     @sort = params[:sort] || 'name'
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 4638f77b887e7440ea0b63a3e7629ff62b919d67..26ba12520c7c0fa48f2c7367facf7c7b99ccbd87 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -1,5 +1,4 @@
 class Projects::BuildsController < Projects::ApplicationController
-  before_action :ci_project
   before_action :build, except: [:index, :cancel_all]
 
   before_action :authorize_manage_builds!, except: [:index, :show, :status]
@@ -9,7 +8,7 @@ class Projects::BuildsController < Projects::ApplicationController
 
   def index
     @scope = params[:scope]
-    @all_builds = project.ci_builds
+    @all_builds = project.builds
     @builds = @all_builds.order('created_at DESC')
     @builds =
       case @scope
@@ -24,13 +23,13 @@ class Projects::BuildsController < Projects::ApplicationController
   end
 
   def cancel_all
-    @project.ci_builds.running_or_pending.each(&:cancel)
+    @project.builds.running_or_pending.each(&:cancel)
 
     redirect_to namespace_project_builds_path(project.namespace, project)
   end
 
   def show
-    @builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC')
+    @builds = @project.ci_commits.find_by_sha(@build.sha).builds.order('id DESC')
     @builds = @builds.where("id not in (?)", @build.id)
     @commit = @build.commit
 
@@ -77,7 +76,7 @@ class Projects::BuildsController < Projects::ApplicationController
   private
 
   def build
-    @build ||= ci_project.builds.unscoped.find_by!(id: params[:id])
+    @build ||= project.builds.unscoped.find_by!(id: params[:id])
   end
 
   def artifacts_file
@@ -85,7 +84,7 @@ class Projects::BuildsController < Projects::ApplicationController
   end
 
   def build_path(build)
-    namespace_project_build_path(build.gl_project.namespace, build.gl_project, build)
+    namespace_project_build_path(build.project.namespace, build.project, build)
   end
 
   def authorize_manage_builds!
diff --git a/app/controllers/projects/ci_services_controller.rb b/app/controllers/projects/ci_services_controller.rb
deleted file mode 100644
index 550a019e8e29037f28a95a75a43585d674e81266..0000000000000000000000000000000000000000
--- a/app/controllers/projects/ci_services_controller.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-class Projects::CiServicesController < Projects::ApplicationController
-  before_action :ci_project
-  before_action :authorize_admin_project!
-
-  layout "project_settings"
-
-  def index
-    @ci_project.build_missing_services
-    @services = @ci_project.services.reload
-  end
-
-  def edit
-    service
-  end
-
-  def update
-    if service.update_attributes(service_params)
-      redirect_to edit_namespace_project_ci_service_path(@project.namespace, @project, service.to_param)
-    else
-      render 'edit'
-    end
-  end
-
-  def test
-    last_build = @project.ci_builds.last
-
-    if service.execute(last_build)
-      message = { notice: 'We successfully tested the service' }
-    else
-      message = { alert: 'We tried to test the service but error occurred' }
-    end
-
-    redirect_back_or_default(options: message)
-  end
-
-  private
-
-  def service
-    @service ||= @ci_project.services.find { |service| service.to_param == params[:id] }
-  end
-
-  def service_params
-    params.require(:service).permit(
-      :type, :active, :webhook, :notify_only_broken_builds,
-      :email_recipients, :email_only_broken_builds, :email_add_pusher,
-      :hipchat_token, :hipchat_room, :hipchat_server
-    )
-  end
-end
diff --git a/app/controllers/projects/ci_settings_controller.rb b/app/controllers/projects/ci_settings_controller.rb
deleted file mode 100644
index a263242a8507daecbcb86932d75b101a48fe2a9c..0000000000000000000000000000000000000000
--- a/app/controllers/projects/ci_settings_controller.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-class Projects::CiSettingsController < Projects::ApplicationController
-  before_action :ci_project
-  before_action :authorize_admin_project!
-
-  layout "project_settings"
-
-  def edit
-  end
-
-  def update
-    if ci_project.update_attributes(project_params)
-      Ci::EventService.new.change_project_settings(current_user, ci_project)
-
-      redirect_to edit_namespace_project_ci_settings_path(project.namespace, project), notice: 'Project was successfully updated.'
-    else
-      render action: "edit"
-    end
-  end
-
-  def destroy
-    ci_project.destroy
-    Ci::EventService.new.remove_project(current_user, ci_project)
-    project.gitlab_ci_service.update_attributes(active: false)
-
-    redirect_to project_path(project), notice: "CI was disabled for this project"
-  end
-
-  protected
-
-  def project_params
-    params.require(:project).permit(:path, :timeout, :timeout_in_minutes, :default_ref, :always_build,
-                                    :polling_interval, :public, :ssh_url_to_repo, :allow_git_fetch, :email_recipients,
-                                    :email_add_pusher, :email_only_broken_builds, :coverage_regex, :shared_runners_enabled, :token,
-                                    { variables_attributes: [:id, :key, :value, :_destroy] })
-  end
-end
diff --git a/app/controllers/projects/ci_web_hooks_controller.rb b/app/controllers/projects/ci_web_hooks_controller.rb
deleted file mode 100644
index a2d470d4a6964143202f620acbf49d8bdb740f52..0000000000000000000000000000000000000000
--- a/app/controllers/projects/ci_web_hooks_controller.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-class Projects::CiWebHooksController < Projects::ApplicationController
-  before_action :ci_project
-  before_action :authorize_admin_project!
-
-  layout "project_settings"
-
-  def index
-    @web_hooks = @ci_project.web_hooks
-    @web_hook = Ci::WebHook.new
-  end
-
-  def create
-    @web_hook = @ci_project.web_hooks.new(web_hook_params)
-    @web_hook.save
-
-    if @web_hook.valid?
-      redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project)
-    else
-      @web_hooks = @ci_project.web_hooks.select(&:persisted?)
-      render :index
-    end
-  end
-
-  def test
-    Ci::TestHookService.new.execute(hook, current_user)
-
-    redirect_back_or_default(default: { action: 'index' })
-  end
-
-  def destroy
-    hook.destroy
-
-    redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project)
-  end
-
-  private
-
-  def hook
-    @web_hook ||= @ci_project.web_hooks.find(params[:id])
-  end
-
-  def web_hook_params
-    params.require(:web_hook).permit(:url)
-  end
-end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index deefdd766678ce6dc16999c3f4902c37755639bf..0aaba3792bf08ead5c260cbe0ba4ceda3b851540 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -31,13 +31,12 @@ class Projects::CommitController < Projects::ApplicationController
   end
 
   def builds
-    @ci_project = @project.gitlab_ci_project
   end
 
   def cancel_builds
     ci_commit.builds.running_or_pending.each(&:cancel)
 
-    redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.sha)
+    redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha)
   end
 
   def retry_builds
@@ -47,7 +46,7 @@ class Projects::CommitController < Projects::ApplicationController
       end
     end
 
-    redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.sha)
+    redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha)
   end
 
   def branches
@@ -67,10 +66,15 @@ class Projects::CommitController < Projects::ApplicationController
   end
 
   def define_show_vars
-    @diffs = commit.diffs
+    if params[:w].to_i == 1
+      @diffs = commit.diffs({ ignore_whitespace_change: true })
+    else
+      @diffs = commit.diffs
+    end
+
     @notes_count = commit.notes.count
-    
-    @builds = ci_commit.builds if ci_commit
+
+    @statuses = ci_commit.statuses if ci_commit
   end
 
   def authorize_manage_builds!
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 58fb946dbc24c8a3a06e663f9b9deddf38dde2fb..04a88990bf4d68557cac40687203cdaca2188d6d 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -9,7 +9,7 @@ class Projects::CommitsController < Projects::ApplicationController
 
   def show
     @repo = @project.repository
-    @limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
+    @limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i
 
     @commits = @repo.commits(@ref, @path, @limit, @offset)
     @note_counts = project.notes.where(commit_id: @commits.map(&:id)).
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 8a785076bb7be6e88c8b0f48de2214ddb48e9bb0..750181f0c1932257d9e60fa465578f986a070d31 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -10,19 +10,35 @@ class Projects::ForksController < Projects::ApplicationController
 
   def create
     namespace = Namespace.find(params[:namespace_key])
-    @forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
+    
+    @forked_project = namespace.projects.find_by(path: project.path)
+    @forked_project = nil unless @forked_project && @forked_project.forked_from_project == project
+
+    @forked_project ||= ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
 
     if @forked_project.saved? && @forked_project.forked?
       if @forked_project.import_in_progress?
-        redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project)
+        redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project, continue: continue_params)
       else
-        redirect_to(
-          namespace_project_path(@forked_project.namespace, @forked_project),
-          notice: 'Project was successfully forked.'
-        )
+        if continue_params
+          redirect_to continue_params[:to], notice: continue_params[:notice]
+        else
+          redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project was successfully forked."
+        end
       end
     else
       render :error
     end
   end
+
+  private
+
+  def continue_params
+    continue_params = params[:continue]
+    if continue_params
+      continue_params.permit(:to, :notice, :notice_now)
+    else
+      nil
+    end
+  end
 end
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index 418b92040bcfc2c84222ffb8f9c9634624e160d9..d13ea9f34b664032a3e04b08ea047fce477b266c 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -5,7 +5,7 @@ class Projects::GraphsController < Projects::ApplicationController
   before_action :require_non_empty_project
   before_action :assign_ref_vars
   before_action :authorize_download_code!
-  before_action :ci_enabled, only: :ci
+  before_action :builds_enabled, only: :ci
 
   def show
     respond_to do |format|
@@ -25,13 +25,31 @@ class Projects::GraphsController < Projects::ApplicationController
   end
 
   def ci
-    ci_project = @project.gitlab_ci_project
-
     @charts = {}
-    @charts[:week] = Ci::Charts::WeekChart.new(ci_project)
-    @charts[:month] = Ci::Charts::MonthChart.new(ci_project)
-    @charts[:year] = Ci::Charts::YearChart.new(ci_project)
-    @charts[:build_times] = Ci::Charts::BuildTime.new(ci_project)
+    @charts[:week] = Ci::Charts::WeekChart.new(project)
+    @charts[:month] = Ci::Charts::MonthChart.new(project)
+    @charts[:year] = Ci::Charts::YearChart.new(project)
+    @charts[:build_times] = Ci::Charts::BuildTime.new(project)
+  end
+
+  def languages
+    @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages
+    total = @languages.map(&:last).sum
+
+    @languages = @languages.map do |language|
+      name, share = language
+      color = Digest::SHA256.hexdigest(name)[0...6]
+      {
+        value: (share.to_f * 100 / total).round(2),
+        label: name,
+        color: "##{color}",
+        highlight: "##{color}"
+      }
+    end
+
+    @languages.sort! do |x, y|
+      y[:value] <=> x[:value]
+    end
   end
 
   private
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index c7569541899602a7695188b326fd57df9256e102..5fd4f855dec8d4073574d53d2b6aff161a64f158 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -25,13 +25,12 @@ class Projects::HooksController < Projects::ApplicationController
 
   def test
     if !@project.empty_repo?
-      status = TestHookService.new.execute(hook, current_user)
+      status, message = TestHookService.new.execute(hook, current_user)
 
       if status
         flash[:notice] = 'Hook successfully executed.'
       else
-        flash[:alert] = 'Hook execution failed. '\
-                        'Ensure hook URL is correct and service is up.'
+        flash[:alert] = "Hook execution failed: #{message}"
       end
     else
       flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
@@ -54,6 +53,7 @@ class Projects::HooksController < Projects::ApplicationController
 
   def hook_params
     params.require(:hook).permit(:url, :push_events, :issues_events,
-      :merge_requests_events, :tag_push_events, :note_events, :enable_ssl_verification)
+      :merge_requests_events, :tag_push_events, :note_events,
+      :build_events, :enable_ssl_verification)
   end
 end
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index fb8788f0818bbbd7d4b022b1dab647ec45035460..8d8035ef5ff9a993b161358a66f1f13e80016367 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -1,7 +1,7 @@
 class Projects::ImportsController < Projects::ApplicationController
   # Authorize
   before_action :authorize_admin_project!
-  before_action :require_no_repo
+  before_action :require_no_repo, except: :show
   before_action :redirect_if_progress, except: :show
 
   def new
@@ -24,21 +24,36 @@ class Projects::ImportsController < Projects::ApplicationController
   end
 
   def show
-    unless @project.import_in_progress?
-      if @project.import_finished?
-        redirect_to(project_path(@project)) and return
+    if @project.repository_exists? || @project.import_finished?
+      if continue_params
+        redirect_to continue_params[:to], notice: continue_params[:notice]
       else
-        redirect_to(new_namespace_project_import_path(@project.namespace,
-                                                      @project)) and return
+        redirect_to project_path(@project), notice: "The project was successfully forked."
       end
+    elsif @project.import_failed?
+      redirect_to new_namespace_project_import_path(@project.namespace, @project)
+    else
+      if continue_params && continue_params[:notice_now]
+        flash.now[:notice] = continue_params[:notice_now]
+      end
+      # Render
     end
   end
 
   private
 
+  def continue_params
+    continue_params = params[:continue]
+    if continue_params
+      continue_params.permit(:to, :notice, :notice_now)
+    else
+      nil
+    end
+  end
+
   def require_no_repo
     if @project.repository_exists? && !@project.import_in_progress?
-      redirect_to(namespace_project_path(@project.namespace, @project)) and return
+      redirect_to(namespace_project_path(@project.namespace, @project))
     end
   end
 
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 5250a0f5e6701efc287177814defc1aadeccc383..b59b52291fb0c90da3c27ff177afcb9b6d830d34 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -58,10 +58,10 @@ class Projects::IssuesController < Projects::ApplicationController
   end
 
   def show
-    @participants = @issue.participants(current_user)
     @note = @project.notes.new(noteable: @issue)
     @notes = @issue.notes.nonawards.with_associations.fresh
     @noteable = @issue
+    @merge_requests = @issue.referenced_merge_requests
 
     respond_with(@issue)
   end
@@ -158,12 +158,10 @@ class Projects::IssuesController < Projects::ApplicationController
   end
 
   def issue_params
-    permitted = params.require(:issue).permit(
+    params.require(:issue).permit(
       :title, :assignee_id, :position, :description,
       :milestone_id, :state_event, :task_num, label_ids: []
     )
-    params[:issue][:title].strip! if params[:issue][:title]
-    permitted
   end
 
   def bulk_update_params
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 6378a1f56b0a631ba045f622ef230774b5bc97b6..ab5c953189cfa6d86123fe9d8ac945a1b3903365 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -1,13 +1,14 @@
 class Projects::MergeRequestsController < Projects::ApplicationController
   before_action :module_enabled
   before_action :merge_request, only: [
-    :edit, :update, :show, :diffs, :commits, :merge, :merge_check,
-    :ci_status, :toggle_subscription
+    :edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
+    :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds
   ]
-  before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
-  before_action :validates_merge_request, only: [:show, :diffs, :commits]
-  before_action :define_show_vars, only: [:show, :diffs, :commits]
-  before_action :ensure_ref_fetched, only: [:show, :commits, :diffs]
+  before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds]
+  before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds]
+  before_action :define_show_vars, only: [:show, :diffs, :commits, :builds]
+  before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check]
+  before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds]
 
   # Allow read any merge_request
   before_action :authorize_read_merge_request!
@@ -79,6 +80,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     end
   end
 
+  def builds
+    respond_to do |format|
+      format.html { render 'show' }
+      format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } }
+    end
+  end
+
   def new
     params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
     @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
@@ -91,20 +99,18 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
     @target_project = merge_request.target_project
     @source_project = merge_request.source_project
-    @commits = @merge_request.compare_commits
+    @commits = @merge_request.compare_commits.reverse
     @commit = @merge_request.last_commit
     @first_commit = @merge_request.first_commit
     @diffs = @merge_request.compare_diffs
+
+    @ci_commit = @merge_request.ci_commit
+    @statuses = @ci_commit.statuses if @ci_commit
+
     @note_counts = Note.where(commit_id: @commits.map(&:id)).
       group(:commit_id).count
   end
 
-  def edit
-    @source_project = @merge_request.source_project
-    @target_project = @merge_request.target_project
-    @target_branches = @merge_request.target_project.repository.branch_names
-  end
-
   def create
     @target_branches ||= []
     @merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
@@ -118,6 +124,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     end
   end
 
+  def edit
+    @source_project = @merge_request.source_project
+    @target_project = @merge_request.target_project
+    @target_branches = @merge_request.target_project.repository.branch_names
+  end
+
   def update
     @merge_request = MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request)
 
@@ -141,24 +153,34 @@ class Projects::MergeRequestsController < Projects::ApplicationController
   end
 
   def merge_check
-    if @merge_request.unchecked?
-      @merge_request.check_if_can_be_merged
-    end
-
-    closes_issues
+    @merge_request.check_if_can_be_merged if @merge_request.unchecked?
 
     render partial: "projects/merge_requests/widget/show.html.haml", layout: false
   end
 
+  def cancel_merge_when_build_succeeds
+    return access_denied! unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user)
+
+    MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user).cancel(@merge_request)
+  end
+
   def merge
     return access_denied! unless @merge_request.can_be_merged_by?(current_user)
 
-    if @merge_request.mergeable?
-      @merge_request.update(merge_error: nil)
-      MergeWorker.perform_async(@merge_request.id, current_user.id, params)
-      @status = true
+    unless @merge_request.mergeable?
+      @status = :failed
+      return
+    end
+
+    @merge_request.update(merge_error: nil)
+
+    if params[:merge_when_build_succeeds].present? && @merge_request.ci_commit && @merge_request.ci_commit.active?
+      MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
+                                                      .execute(@merge_request)
+      @status = :merge_when_build_succeeds
     else
-      @status = false
+      MergeWorker.perform_async(@merge_request.id, current_user.id, params)
+      @status = :success
     end
   end
 
@@ -250,8 +272,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
   end
 
   def define_show_vars
-    @participants = @merge_request.participants(current_user)
-
     # Build a note object for comment form
     @note = @project.notes.new(noteable: @merge_request)
     @notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
@@ -264,25 +284,35 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
     @merge_request_diff = @merge_request.merge_request_diff
 
+    @ci_commit = @merge_request.ci_commit
+    @statuses = @ci_commit.statuses if @ci_commit
+
     if @merge_request.locked_long_ago?
       @merge_request.unlock_mr
       @merge_request.close
     end
   end
 
+  def define_widget_vars
+    @ci_commit = @merge_request.ci_commit
+    closes_issues
+  end
+
   def invalid_mr
     # Render special view for MR with removed source or target branch
     render 'invalid'
   end
 
   def merge_request_params
-    permitted = params.require(:merge_request).permit(
+    params.require(:merge_request).permit(
       :title, :assignee_id, :source_project_id, :source_branch,
       :target_project_id, :target_branch, :milestone_id,
       :state_event, :description, :task_num, label_ids: []
     )
-    params[:merge_request][:title].strip! if params[:merge_request][:title]
-    permitted
+  end
+
+  def merge_params
+    params.permit(:should_remove_source_branch, :commit_message)
   end
 
   # Make sure merge requests created before 8.0
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 5ac18446aa7dccb01bf63685da25ee1d7f3b8cd3..6f1e186d4084d4eb4729a74b989785c48115ad6a 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -13,7 +13,8 @@ class Projects::NotesController < Projects::ApplicationController
     @notes.each do |note|
       notes_json[:notes] << {
         id: note.id,
-        html: note_to_html(note)
+        html: note_to_html(note),
+        valid: note.valid?
       }
     end
 
@@ -68,7 +69,7 @@ class Projects::NotesController < Projects::ApplicationController
     data = {
       author: current_user,
       is_award: true,
-      note: note_params[:note].gsub(":", '')
+      note: note_params[:note].delete(":")
     }
 
     note = noteable.notes.find_by(data)
@@ -131,16 +132,24 @@ class Projects::NotesController < Projects::ApplicationController
   end
 
   def render_note_json(note)
-    render json: {
-      id: note.id,
-      discussion_id: note.discussion_id,
-      html: note_to_html(note),
-      award: note.is_award,
-      emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
-      note: note.note,
-      discussion_html: note_to_discussion_html(note),
-      discussion_with_diff_html: note_to_discussion_with_diff_html(note)
-    }
+    if note.valid?
+      render json: {
+        valid: true,
+        id: note.id,
+        discussion_id: note.discussion_id,
+        html: note_to_html(note),
+        award: note.is_award,
+        note: note.note,
+        discussion_html: note_to_discussion_html(note),
+        discussion_with_diff_html: note_to_discussion_with_diff_html(note)
+      }
+    else
+      render json: {
+        valid: false,
+        award: note.is_award,
+        errors: note.errors
+      }
+    end
   end
 
   def authorize_admin_note!
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 07eb94e4f48dd9967b7ac435f73702f56e661c30..8364fc293b748f8402d7fb1ba29c5ad2b8c7e191 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -23,7 +23,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
         @group_members = @group_members.where(user_id: users)
       end
 
-      @group_members = @group_members.order('access_level DESC').limit(20)
+      @group_members = @group_members.order('access_level DESC')
     end
 
     @project_member = @project.project_members.new
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index 6b52eccebf73a6f49406052ad28994b82324a39c..e49259c34b6d6c66be68593e72acc969f443695b 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -21,7 +21,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
 
     if protected_branch &&
        protected_branch.update_attributes(
-        developers_can_push: params[:developers_can_push]
+         developers_can_push: params[:developers_can_push]
        )
 
       respond_to do |format|
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index d5ee6ac8663b24aadaa88818becf516764454741..be7d5c187feae79d67fd6d24e8b032030b35ca8f 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -10,15 +10,13 @@ class Projects::RawController < Projects::ApplicationController
     @blob = @repository.blob_at(@commit.id, @path)
 
     if @blob
-      type = get_blob_type
-
       headers['X-Content-Type-Options'] = 'nosniff'
 
-      send_data(
-        @blob.data,
-        type: type,
-        disposition: 'inline'
-      )
+      if @blob.lfs_pointer?
+        send_lfs_object
+      else
+        stream_data
+      end
     else
       render_404
     end
@@ -35,4 +33,33 @@ class Projects::RawController < Projects::ApplicationController
       'application/octet-stream'
     end
   end
+
+  def stream_data
+    type = get_blob_type
+
+    send_data(
+      @blob.data,
+      type: type,
+      disposition: 'inline'
+    )
+  end
+
+  def send_lfs_object
+    lfs_object = find_lfs_object
+
+    if lfs_object && lfs_object.project_allowed_access?(@project)
+      send_file lfs_object.file.path, filename: @blob.name, disposition: 'attachment'
+    else
+      render_404
+    end
+  end
+
+  def find_lfs_object
+    lfs_object = LfsObject.find_by_oid(@blob.lfs_oid)
+    if lfs_object && lfs_object.file.exists?
+      lfs_object
+    else
+      nil
+    end
+  end
 end
diff --git a/app/controllers/projects/runner_projects_controller.rb b/app/controllers/projects/runner_projects_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e2785caa2fb0301c9a6b839d0af4a9857972ab75
--- /dev/null
+++ b/app/controllers/projects/runner_projects_controller.rb
@@ -0,0 +1,26 @@
+class Projects::RunnerProjectsController < Projects::ApplicationController
+  before_action :authorize_admin_project!
+
+  layout 'project_settings'
+
+  def create
+    @runner = Ci::Runner.find(params[:runner_project][:runner_id])
+
+    return head(403) unless current_user.ci_authorized_runners.include?(@runner)
+
+    path = runners_path(project)
+
+    if @runner.assign_to(project, current_user)
+      redirect_to path
+    else
+      redirect_to path, alert: 'Failed adding runner to project'
+    end
+  end
+
+  def destroy
+    runner_project = project.runner_projects.find(params[:id])
+    runner_project.destroy
+
+    redirect_to runners_path(project)
+  end
+end
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index bfbcf2567f3a2f1f693941a9dfcadc916bda26c4..4993b2648a55981a5bbe0523c4c4e766983162b7 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -1,14 +1,13 @@
 class Projects::RunnersController < Projects::ApplicationController
-  before_action :ci_project
   before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
   before_action :authorize_admin_project!
 
   layout 'project_settings'
 
   def index
-    @runners = @ci_project.runners.ordered
+    @runners = project.runners.ordered
     @specific_runners = current_user.ci_authorized_runners.
-      where.not(id: @ci_project.runners).
+      where.not(id: project.runners).
       ordered.page(params[:page]).per(20)
     @shared_runners = Ci::Runner.shared.active
     @shared_runners_count = @shared_runners.count(:all)
@@ -26,7 +25,7 @@ class Projects::RunnersController < Projects::ApplicationController
   end
 
   def destroy
-    if @runner.only_for?(@ci_project)
+    if @runner.only_for?(project)
       @runner.destroy
     end
 
@@ -52,10 +51,16 @@ class Projects::RunnersController < Projects::ApplicationController
   def show
   end
 
+  def toggle_shared_runners
+    project.toggle!(:shared_runners_enabled)
+
+    redirect_to namespace_project_runners_path(project.namespace, project)
+  end
+
   protected
 
   def set_runner
-    @runner ||= @ci_project.runners.find(params[:id])
+    @runner ||= project.runners.find(params[:id])
   end
 
   def runner_params
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 42dbb497e013ace446d1b38e76c37c9e84b35124..8b2577aebe1e537dc368cac0598bfc1154650c30 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -1,14 +1,17 @@
 class Projects::ServicesController < Projects::ApplicationController
-  ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :subdomain,
+  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, :send_from_committer_email, :disable_diffs, :external_wiki_url,
+                    :note_events, :build_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]
+                    :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]
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index cb39c2b878283dba5663642c7da542caab4dcbea..280fe12cc7cd43c7c543bf7c5ee8f2176949ff9f 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -2,7 +2,7 @@ class Projects::TagsController < Projects::ApplicationController
   # Authorize
   before_action :require_non_empty_project
   before_action :authorize_download_code!
-  before_action :authorize_push_code!, only: [:create]
+  before_action :authorize_push_code!, only: [:new, :create]
   before_action :authorize_admin_project!, only: [:destroy]
 
   def index
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 8f272ad1281fef7306c646248ff67acf1f18465d..cb3ed0f6f9c25dabfb05247f6002bf2478330a3d 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -1,14 +1,14 @@
 # Controller for viewing a repository's file structure
 class Projects::TreeController < Projects::ApplicationController
   include ExtractsPath
-  include CreatesMergeRequestForCommit
+  include CreatesCommit
   include ActionView::Helpers::SanitizeHelper
 
   before_action :require_non_empty_project, except: [:new, :create]
   before_action :assign_ref_vars
   before_action :assign_dir_vars, only: [:create_dir]
   before_action :authorize_download_code!
-  before_action :authorize_push_code!, only: [:create_dir]
+  before_action :authorize_edit_tree!, only: [:create_dir]
 
   def show
     return render_404 unless @repository.commit(@ref)
@@ -34,44 +34,20 @@ class Projects::TreeController < Projects::ApplicationController
   def create_dir
     return render_404 unless @commit_params.values.all?
 
-    begin
-      result = Files::CreateDirService.new(@project, current_user, @commit_params).execute
-      message = result[:message]
-    rescue => e
-      message = e.to_s
-    end
-
-    if result && result[:status] == :success
-      flash[:notice] = "The directory has been successfully created"
-      respond_to do |format|
-        format.html { redirect_to after_create_dir_path }
-      end
-    else
-      flash[:alert] = message
-      respond_to do |format|
-        format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, @new_branch) }
-      end
-    end
+    create_commit(Files::CreateDirService,  success_notice: "The directory has been successfully created.",
+                                            success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@target_branch, @dir_name)),
+                                            failure_path: namespace_project_tree_path(@project.namespace, @project, @ref))
   end
 
   private
 
   def assign_dir_vars
-    @new_branch = params[:new_branch].present? ? sanitize(strip_tags(params[:new_branch])) : @ref
+    @target_branch = params[:target_branch]
+
     @dir_name = File.join(@path, params[:dir_name])
     @commit_params = {
       file_path: @dir_name,
-      current_branch: @ref,
-      target_branch: @new_branch,
       commit_message: params[:commit_message],
     }
   end
-
-  def after_create_dir_path
-    if create_merge_request?
-      new_merge_request_path
-    else
-      namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name))
-    end
-  end
 end
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index 782ebd01b055dc7298ee3fb6a0ddcee81a801529..30adfad1daa9f7fad7f97988d2182273a1411c0a 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -1,22 +1,21 @@
 class Projects::TriggersController < Projects::ApplicationController
-  before_action :ci_project
   before_action :authorize_admin_project!
 
   layout 'project_settings'
 
   def index
-    @triggers = @ci_project.triggers
+    @triggers = project.triggers
     @trigger = Ci::Trigger.new
   end
 
   def create
-    @trigger = @ci_project.triggers.new
+    @trigger = project.triggers.new
     @trigger.save
 
     if @trigger.valid?
       redirect_to namespace_project_triggers_path(@project.namespace, @project)
     else
-      @triggers = @ci_project.triggers.select(&:persisted?)
+      @triggers = project.triggers.select(&:persisted?)
       render :index
     end
   end
@@ -30,6 +29,6 @@ class Projects::TriggersController < Projects::ApplicationController
   private
 
   def trigger
-    @trigger ||= @ci_project.triggers.find(params[:id])
+    @trigger ||= project.triggers.find(params[:id])
   end
 end
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index d6561a45a701f52fb616869f0ce0bd92cc256061..10efafea9db71657ba77d15e7e9cfe40e0d4d1d9 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -1,5 +1,4 @@
 class Projects::VariablesController < Projects::ApplicationController
-  before_action :ci_project
   before_action :authorize_admin_project!
 
   layout 'project_settings'
@@ -8,9 +7,7 @@ class Projects::VariablesController < Projects::ApplicationController
   end
 
   def update
-    if ci_project.update_attributes(project_params)
-      Ci::EventService.new.change_project_settings(current_user, ci_project)
-
+    if project.update_attributes(project_params)
       redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.'
     else
       render action: 'show'
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 23453195e8521a3603beca537b2503432a6b8c19..3004722bce033664de15775ab47227daf3e02f5b 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -123,7 +123,7 @@ class ProjectsController < ApplicationController
     ::Projects::DestroyService.new(@project, current_user, {}).execute
     flash[:alert] = "Project '#{@project.name}' was deleted."
 
-    redirect_back_or_default(default: dashboard_projects_path, options: {})
+    redirect_to dashboard_projects_path
   rescue Projects::DestroyService::DestroyError => ex
     redirect_to edit_project_path(@project), alert: ex.message
   end
@@ -171,14 +171,14 @@ class ProjectsController < ApplicationController
     @project.reload
 
     render json: {
-      html: view_to_html_string("projects/buttons/_star")
+      star_count: @project.star_count
     }
   end
 
   def markdown_preview
     text = params[:text]
 
-    ext = Gitlab::ReferenceExtractor.new(@project, current_user)
+    ext = Gitlab::ReferenceExtractor.new(@project, current_user, current_user)
     ext.analyze(text)
 
     render json: {
@@ -210,10 +210,10 @@ class ProjectsController < ApplicationController
 
   def project_params
     params.require(:project).permit(
-      :name, :path, :description, :issues_tracker, :tag_list,
+      :name, :path, :description, :issues_tracker, :tag_list, :runners_token,
       :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
       :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
-      :builds_enabled
+      :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
     )
   end
 
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 3b3dc86cb6852d64ee3855c0ebd43b609f8c519c..c48175a4c5ab25fb73277740ccab91e9e2df1bef 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -1,10 +1,21 @@
 class RegistrationsController < Devise::RegistrationsController
   before_action :signup_enabled?
+  include Recaptcha::Verify
 
   def new
     redirect_to(new_user_session_path)
   end
 
+  def create
+    if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
+      super
+    else
+      flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code."
+      flash.delete :recaptcha_error
+      render action: 'new'
+    end
+  end
+
   def destroy
     DeleteUserService.new(current_user).execute(current_user)
 
@@ -38,4 +49,16 @@ class RegistrationsController < Devise::RegistrationsController
   def sign_up_params
     params.require(:user).permit(:username, :email, :name, :password, :password_confirmation)
   end
+
+  def resource_name
+    :user
+  end
+
+  def resource
+    @resource ||= User.new(sign_up_params)
+  end
+
+  def devise_mapping
+    @devise_mapping ||= Devise.mappings[:user]
+  end
 end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 1b60d3e27d0ae170a410d8ab43b9109b5e66695d..825f85199bef03a33d4daa608bf0f5c38c88c06d 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -1,9 +1,11 @@
 class SessionsController < Devise::SessionsController
   include AuthenticatesWithTwoFactor
+  include Recaptcha::ClientHelper
 
   prepend_before_action :authenticate_with_two_factor, only: [:create]
   prepend_before_action :store_redirect_path, only: [:new]
   before_action :auto_sign_in_with_provider, only: [:new]
+  before_action :load_recaptcha
 
   def new
     if Gitlab.config.ldap.enabled
@@ -40,7 +42,7 @@ class SessionsController < Devise::SessionsController
       User.find(session[:otp_user_id])
     end
   end
-  
+
   def store_redirect_path
     redirect_path =
       if request.referer.present? && (params['redirect_to_referer'] == 'yes')
@@ -87,14 +89,14 @@ class SessionsController < Devise::SessionsController
     provider = Gitlab.config.omniauth.auto_sign_in_with_provider
     return unless provider.present?
 
-    # Auto sign in with an Omniauth provider only if the standard "you need to sign-in" alert is 
-    # registered or no alert at all. In case of another alert (such as a blocked user), it is safer  
+    # Auto sign in with an Omniauth provider only if the standard "you need to sign-in" alert is
+    # registered or no alert at all. In case of another alert (such as a blocked user), it is safer
     # to do nothing to prevent redirection loops with certain Omniauth providers.
     return unless flash[:alert].blank? || flash[:alert] == I18n.t('devise.failure.unauthenticated')
-    
+
     # Prevent alert from popping up on the first page shown after authentication.
-    flash[:alert] = nil 
-    
+    flash[:alert] = nil
+
     redirect_to user_omniauth_authorize_path(provider.to_sym)
   end
 
@@ -107,4 +109,8 @@ class SessionsController < Devise::SessionsController
     AuditEventService.new(user, user, options).
       for_authentication.security_event
   end
+
+  def load_recaptcha
+    Gitlab::Recaptcha.load_configurations!
+  end
 end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 08f2483af33e60ebfe505bc908cebb1a8ea76768..c72df73af46856e2f23184a602034ff403a611da 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -2,7 +2,7 @@ class SnippetsController < ApplicationController
   before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
 
   # Allow read snippet
-  before_action :authorize_read_snippet!, only: [:show]
+  before_action :authorize_read_snippet!, only: [:show, :raw]
 
   # Allow modify snippet
   before_action :authorize_update_snippet!, only: [:edit, :update]
diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb
index b704e878903b5f460d0d8b3a33955e3f2d051829..630c73c2a94bd79f63e89a861614b9fe3a26ad3b 100644
--- a/app/finders/milestones_finder.rb
+++ b/app/finders/milestones_finder.rb
@@ -1,7 +1,7 @@
 class MilestonesFinder
   def execute(projects, params)
     milestones = Milestone.of_projects(projects)
-    milestones = milestones.order("due_date ASC")
+    milestones = milestones.reorder("due_date ASC")
 
     case params[:state]
     when 'closed' then milestones.closed
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 8ecdeaf8e76ba8fe241838106c6ed9bc9509579c..f7f7a1a02d3ef00d3ce475c35fa23f95542acbaa 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -61,29 +61,29 @@ module ApplicationHelper
     options[:class] ||= ''
     options[:class] << ' identicon'
     bg_key = project.id % 7
-    style = "background-color: ##{ allowed_colors.values[bg_key] }; color: #555"
+    style = "background-color: ##{allowed_colors.values[bg_key]}; color: #555"
 
     content_tag(:div, class: options[:class], style: style) do
       project.name[0, 1].upcase
     end
   end
 
-  def avatar_icon(user_or_email = nil, size = nil)
+  def avatar_icon(user_or_email = nil, size = nil, scale = 2)
     if user_or_email.is_a?(User)
       user = user_or_email
     else
-      user = User.find_by(email: user_or_email)
+      user = User.find_by(email: user_or_email.downcase)
     end
 
     if user
       user.avatar_url(size) || default_avatar
     else
-      gravatar_icon(user_or_email, size)
+      gravatar_icon(user_or_email, size, scale)
     end
   end
 
-  def gravatar_icon(user_email = '', size = nil)
-    GravatarService.new.execute(user_email, size) ||
+  def gravatar_icon(user_email = '', size = nil, scale = 2)
+    GravatarService.new.execute(user_email, size, scale) ||
       default_avatar
   end
 
@@ -204,12 +204,16 @@ module ApplicationHelper
   # Returns an HTML-safe String
   def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false)
     element = content_tag :time, time.to_s,
-      class: "#{html_class} js-timeago",
+      class: "#{html_class} js-timeago js-timeago-pending",
       datetime: time.getutc.iso8601,
       title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
       data: { toggle: 'tooltip', placement: placement, container: 'body' }
 
-    element += javascript_tag "$('.js-timeago').timeago()" unless skip_js
+    unless skip_js
+      element << javascript_tag(
+        "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()"
+      )
+    end
 
     element
   end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 2c81ea1623c52f9945519e121e0df1f839926ec1..0cfc0565e84fb50836609e58c4143cfe2ae2ef45 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -50,5 +50,17 @@ module AuthHelper
     current_user.identities.exists?(provider: provider.to_s)
   end
 
+  def two_factor_skippable?
+    current_application_settings.require_two_factor_authentication &&
+      !current_user.two_factor_enabled &&
+      current_application_settings.two_factor_grace_period &&
+      !two_factor_grace_period_expired?
+  end
+
+  def two_factor_grace_period_expired?
+    current_user.otp_grace_period_started_at &&
+      (current_user.otp_grace_period_started_at + current_application_settings.two_factor_grace_period.hours) < Time.current
+  end
+
   extend self
 end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 77d99140c43966073d36a45a37a0ddad2ade8b09..d31d4cde08f4dcb2579d2fd582c50111401e9bca 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -22,36 +22,92 @@ module BlobHelper
     %w(credits changelog news copying copyright license authors)
   end
 
-  def edit_blob_link(project, ref, path, options = {})
-    blob =
-      begin
-        project.repository.blob_at(ref, path)
-      rescue
-        nil
-      end
-
-    if blob && blob.text?
-      text = 'Edit'
-      after = options[:after] || ''
-      from_mr = options[:from_merge_request_id]
-      link_opts = {}
-      link_opts[:from_merge_request_id] = from_mr if from_mr
-      cls = 'btn btn-small'
-      if allowed_tree_edit?(project, ref)
-        link_to(text,
-                namespace_project_edit_blob_path(project.namespace, project,
-                                                 tree_join(ref, path),
-                                                 link_opts),
-                class: cls
-               )
-      else
-        content_tag :span, text, class: cls + ' disabled'
-      end + after.html_safe
-    else
-      ''
+  def edit_blob_link(project = @project, ref = @ref, path = @path, options = {})
+    return unless current_user
+
+    blob = project.repository.blob_at(ref, path) rescue nil
+
+    return unless blob && blob_text_viewable?(blob)
+
+    from_mr = options[:from_merge_request_id]
+    link_opts = {}
+    link_opts[:from_merge_request_id] = from_mr if from_mr
+
+    edit_path = namespace_project_edit_blob_path(project.namespace, project,
+                                     tree_join(ref, path),
+                                     link_opts)
+
+    if !on_top_of_branch?
+      button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
+    elsif can_edit_blob?(blob)
+      link_to "Edit", edit_path, class: 'btn btn-small'
+    elsif can?(current_user, :fork_project, project)
+      continue_params = {
+        to:     edit_path,
+        notice: edit_in_new_fork_notice,
+        notice_now: edit_in_new_fork_notice_now
+      }
+      fork_path = namespace_project_fork_path(project.namespace, project, namespace_key:  current_user.namespace.id,
+                                                                          continue:       continue_params)
+
+      link_to "Edit", fork_path, class: 'btn btn-small', method: :post
+    end
+  end
+
+  def modify_file_link(project = @project, ref = @ref, path = @path, label:, action:, btn_class:, modal_type:)
+    return unless current_user
+
+    blob = project.repository.blob_at(ref, path) rescue nil
+
+    return unless blob
+
+    if !on_top_of_branch?
+      button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
+    elsif blob.lfs_pointer?
+      button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
+    elsif can_edit_blob?(blob)
+      button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
+    elsif can?(current_user, :fork_project, project)
+      continue_params = {
+        to:     request.fullpath,
+        notice: edit_in_new_fork_notice + " Try to #{action} this file again.",
+        notice_now: edit_in_new_fork_notice_now
+      }
+      fork_path = namespace_project_fork_path(project.namespace, project, namespace_key:  current_user.namespace.id,
+                                                                          continue:       continue_params)
+
+      link_to label, fork_path, class: "btn btn-#{btn_class}", method: :post
     end
   end
 
+  def replace_blob_link(project = @project, ref = @ref, path = @path)
+    modify_file_link(
+      project,
+      ref,
+      path,
+      label:      "Replace",
+      action:     "replace",
+      btn_class:  "default",
+      modal_type: "upload"
+    )
+  end
+
+  def delete_blob_link(project = @project, ref = @ref, path = @path)
+    modify_file_link(
+      project,
+      ref,
+      path,
+      label:      "Delete",
+      action:     "delete",
+      btn_class:  "remove",
+      modal_type: "remove"
+    )
+  end
+
+  def can_edit_blob?(blob, project = @project, ref = @ref)
+    !blob.lfs_pointer? && can_edit_tree?(project, ref)
+  end
+
   def leave_edit_message
     "Leave edit mode?\nAll unsaved changes will be lost."
   end
@@ -60,7 +116,7 @@ module BlobHelper
     if Gitlab::MarkupHelper.previewable?(filename)
       'Preview'
     else
-      'Preview changes'
+      'Preview Changes'
     end
   end
 
@@ -71,4 +127,16 @@ module BlobHelper
   def blob_icon(mode, name)
     icon("#{file_type_icon_class('file', mode, name)} fw")
   end
+
+  def blob_text_viewable?(blob)
+    blob && blob.text? && !blob.lfs_pointer?
+  end
+
+  def blob_size(blob)
+    if blob.lfs_pointer?
+      blob.lfs_size
+    else
+      blob.size
+    end
+  end
 end
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index d6eaa7d57bcafcbf6496187e66cf58ea5252bb3d..e39548e17e1ca0a64288ba0e8b6016d687c1f76b 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -11,7 +11,7 @@ module BranchesHelper
 
   def can_push_branch?(project, branch_name)
     return false unless project.repository.branch_names.include?(branch_name)
-    
+
     ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
   end
 end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ec0e3f409c1596e03e5f344748ea562ddaca4902
--- /dev/null
+++ b/app/helpers/button_helper.rb
@@ -0,0 +1,58 @@
+module ButtonHelper
+  # Output a "Copy to Clipboard" button
+  #
+  # data - Data attributes passed to `content_tag`
+  #
+  # Examples:
+  #
+  #   # Define the clipboard's text
+  #   clipboard_button(clipboard_text: "Foo")
+  #   # => "<button class='...' data-clipboard-text='Foo'>...</button>"
+  #
+  #   # Define the target element
+  #   clipboard_button(clipboard_target: "div#foo")
+  #   # => "<button class='...' data-clipboard-target='div#foo'>...</button>"
+  #
+  # See http://clipboardjs.com/#usage
+  def clipboard_button(data = {})
+    content_tag :button,
+      icon('clipboard'),
+      class: 'btn btn-xs btn-clipboard',
+      data: data,
+      type: :button
+  end
+
+  def http_clone_button(project)
+    klass = 'btn js-protocol-switch'
+    klass << ' active'      if default_clone_protocol == 'http'
+    klass << ' has_tooltip' if current_user.try(:require_password?)
+
+    protocol = gitlab_config.protocol.upcase
+
+    content_tag :button, protocol,
+      class: klass,
+      data: {
+        clone: project.http_url_to_repo,
+        container: 'body',
+        html: 'true',
+        title: "Set a password on your account<br>to pull or push via #{protocol}"
+      },
+      type: :button
+  end
+
+  def ssh_clone_button(project)
+    klass = 'btn js-protocol-switch'
+    klass << ' active'      if default_clone_protocol == 'ssh'
+    klass << ' has_tooltip' if current_user.try(:require_ssh_key?)
+
+    content_tag :button, 'SSH',
+      class: klass,
+      data: {
+        clone: project.ssh_url_to_repo,
+        container: 'body',
+        html: 'true',
+        title: 'Add an SSH key to your profile<br>to pull or push via SSH.'
+      },
+      type: :button
+  end
+end
diff --git a/app/helpers/ci/gitlab_helper.rb b/app/helpers/ci/gitlab_helper.rb
deleted file mode 100644
index e34c8be1dfc5249f7cd40f9023358fb63328806b..0000000000000000000000000000000000000000
--- a/app/helpers/ci/gitlab_helper.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module Ci
-  module GitlabHelper
-    def no_turbolink
-      { :"data-no-turbolink" => "data-no-turbolink" }
-    end
-
-    def yaml_web_editor_link(project)
-      commits = project.commits
-
-      if commits.any? && commits.last.ci_yaml_file
-        "#{project.gitlab_url}/edit/master/.gitlab-ci.yml"
-      else
-        "#{project.gitlab_url}/new/master"
-      end
-    end
-  end
-end
diff --git a/app/helpers/ci/projects_helper.rb b/app/helpers/ci/projects_helper.rb
deleted file mode 100644
index fd991a4165a1f811893574901f84dbf0637892c1..0000000000000000000000000000000000000000
--- a/app/helpers/ci/projects_helper.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module Ci
-  module ProjectsHelper
-    def ref_tab_class ref = nil
-      'active' if ref == @ref
-    end
-
-    def success_ratio(success_builds, failed_builds)
-      failed_builds = failed_builds.count(:all)
-      success_builds = success_builds.count(:all)
-
-      return 100 if failed_builds.zero?
-
-      ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100
-      ratio.to_i
-    end
-
-    def markdown_badge_code(project, ref)
-      url = status_ci_project_url(project, ref: ref, format: 'png')
-      "[![build status](#{url})](#{ci_project_url(project, ref: ref)})"
-    end
-
-    def html_badge_code(project, ref)
-      url = status_ci_project_url(project, ref: ref, format: 'png')
-      "<a href='#{ci_project_url(project, ref: ref)}'><img src='#{url}' /></a>"
-    end
-
-    def project_uses_specific_runner?(project)
-      project.runners.any?
-    end
-
-    def no_runners_for_project?(project)
-      project.runners.blank? &&
-        Ci::Runner.shared.blank?
-    end
-  end
-end
diff --git a/app/helpers/ci_badge_helper.rb b/app/helpers/ci_badge_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..27386133e36b1df5146e23785e8ba65852525e19
--- /dev/null
+++ b/app/helpers/ci_badge_helper.rb
@@ -0,0 +1,13 @@
+module CiBadgeHelper
+  def markdown_badge_code(project, ref)
+    url = status_ci_project_url(project, ref: ref, format: 'png')
+    link = namespace_project_commits_path(project.namespace, project, ref)
+    "[![build status](#{url})](#{link})"
+  end
+
+  def html_badge_code(project, ref)
+    url = status_ci_project_url(project, ref: ref, format: 'png')
+    link = namespace_project_commits_path(project.namespace, project, ref)
+    "<a href='#{link}'><img src='#{url}' /></a>"
+  end
+end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 0ecf77bb45ee99052bb664d631f19bdffa462c83..d8bee21c82e95753236f6b6701ed0249411f7060 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -1,6 +1,6 @@
 module CiStatusHelper
   def ci_status_path(ci_commit)
-    project = ci_commit.gl_project
+    project = ci_commit.project
     builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
   end
 
@@ -8,22 +8,21 @@ module CiStatusHelper
     ci_icon_for_status(ci_commit.status)
   end
 
-  def ci_status_color(ci_commit)
-    case ci_commit.status
-    when 'success'
-      'green'
-    when 'failed'
-      'red'
-    when 'running', 'pending'
-      'yellow'
-    else
-      'gray'
-    end
+  def ci_status_label(ci_commit)
+    ci_label_for_status(ci_commit.status)
   end
 
   def ci_status_with_icon(status)
     content_tag :span, class: "ci-status ci-#{status}" do
-      ci_icon_for_status(status) + '&nbsp;'.html_safe + status
+      ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
+    end
+  end
+
+  def ci_label_for_status(status)
+    if status == 'success'
+      'passed'
+    else
+      status
     end
   end
 
@@ -40,15 +39,19 @@ module CiStatusHelper
         'circle'
       end
 
-    icon(icon_name)
+    icon(icon_name + ' fw')
   end
 
   def render_ci_status(ci_commit)
-    link_to ci_status_path(ci_commit),
-      class: "c#{ci_status_color(ci_commit)}",
-      title: "Build status: #{ci_commit.status}",
-      data: { toggle: 'tooltip', placement: 'left' } do
-      ci_status_icon(ci_commit)
-    end
+    link_to ci_status_icon(ci_commit),
+      ci_status_path(ci_commit),
+      class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
+      title: "Build #{ci_status_label(ci_commit)}",
+      data: { toggle: 'tooltip', placement: 'left' }
+  end
+
+  def no_runners_for_project?(project)
+    project.runners.blank? &&
+      Ci::Runner.shared.blank?
   end
 end
diff --git a/app/helpers/clipboard_helper.rb b/app/helpers/clipboard_helper.rb
deleted file mode 100644
index 3c1d7569fac3df89c38b464502382bd4763e521b..0000000000000000000000000000000000000000
--- a/app/helpers/clipboard_helper.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-module ClipboardHelper
-  def clipboard_button
-    content_tag :button,
-      icon('clipboard'),
-      class: 'btn btn-xs btn-clipboard js-clipboard-trigger',
-      type: :button
-  end
-end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 9df20c9fce5d7b381d956dd65ba15805fa96d47f..590d20ac7b3495f37611935c015db301f2fdbce4 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -109,7 +109,7 @@ module CommitsHelper
         )
       elsif @path.present?
         return link_to(
-          "Browse Dir »",
+          "Browse Directory »",
           namespace_project_tree_path(project.namespace, project,
                                       tree_join(commit.id, @path)),
           class: "pull-right"
@@ -117,7 +117,7 @@ module CommitsHelper
       end
     end
     link_to(
-      "Browse Code »",
+      "Browse Files »",
       namespace_project_tree_path(project.namespace, project, commit),
       class: "pull-right"
     )
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index bfd3622a6a985af6eb8ff900f66bcd3b95cfd6e6..24134310fc5d084b4cfa15f1e1c6009b9c51f476 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -146,9 +146,9 @@ module DiffHelper
   def submodule_link(blob, ref, repository = @repository)
     tree, commit = submodule_links(blob, ref, repository)
     commit_id = if commit.nil?
-                  blob.id[0..10]
+                  Commit.truncate_sha(blob.id)
                 else
-                  link_to "#{blob.id[0..10]}", commit
+                  link_to Commit.truncate_sha(blob.id), commit
                 end
 
     [
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 45788ba95ac889eca40e85d23ff15549136a1ae8..41b5bd7be9060b5c7b658a92680a7e9bc9e2ffc8 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -28,6 +28,8 @@ module EmailsHelper
         return "View #{action.humanize.singularize}"
       end
     end
+
+    nil
   end
 
   def color_email_diff(diffcontent)
diff --git a/app/helpers/external_wiki_helper.rb b/app/helpers/external_wiki_helper.rb
index 838b85afdfe573e1d605c784fe47b35731aa9894..1f3401f290637587be4ee512738b864885220569 100644
--- a/app/helpers/external_wiki_helper.rb
+++ b/app/helpers/external_wiki_helper.rb
@@ -1,7 +1,7 @@
 module ExternalWikiHelper
   def get_project_wiki_path(project)
     external_wiki_service = project.services.
-      select { |service| service.to_param == 'external_wiki' }.first
+      find { |service| service.to_param == 'external_wiki' }
     if external_wiki_service.present? && external_wiki_service.active?
       external_wiki_service.properties['external_wiki_url']
     else
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 98c6d9d5d2ece0660c58b92194537cda308a1d74..ca41657cec152fbdc6f7f90fa29b09cc7a5f6aec 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -20,7 +20,7 @@ module GitlabMarkdownHelper
                    end
 
     user = current_user if defined?(current_user)
-    gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: user)
+    gfm_body = Banzai.render(escaped_body, project: @project, current_user: user, pipeline: :single_line)
 
     fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
     if fragment.children.size == 1 && fragment.children[0].name == 'a'
@@ -46,23 +46,36 @@ module GitlabMarkdownHelper
   end
 
   def markdown(text, context = {})
-    process_markdown(text, context)
-  end
+    return "" unless text.present?
+
+    context[:project] ||= @project
 
-  # TODO (rspeicher): Remove all usages of this helper and just call `markdown`
-  # with a custom pipeline depending on the content being rendered
-  def gfm(text, options = {})
-    process_markdown(text, options, :gfm)
+    html = Banzai.render(text, context)
+
+    context.merge!(
+      current_user:   (current_user if defined?(current_user)),
+
+      # RelativeLinkFilter
+      requested_path: @path,
+      project_wiki:   @project_wiki,
+      ref:            @ref
+    )
+
+    Banzai.post_process(html, context)
   end
 
   def asciidoc(text)
-    Gitlab::Asciidoc.render(text, {
-      commit: @commit,
-      project: @project,
-      project_wiki: @project_wiki,
+    Gitlab::Asciidoc.render(
+      text,
+      project:      @project,
+      current_user: (current_user if defined?(current_user)),
+
+      # RelativeLinkFilter
+      project_wiki:   @project_wiki,
       requested_path: @path,
-      ref: @ref
-    })
+      ref:            @ref,
+      commit:         @commit
+    )
   end
 
   # Return the first line of +text+, up to +max_chars+, after parsing the line
@@ -178,26 +191,4 @@ module GitlabMarkdownHelper
       ''
     end
   end
-
-  def process_markdown(text, options, method = :markdown)
-    return "" unless text.present?
-
-    options.reverse_merge!(
-      path:         @path,
-      pipeline:     :default,
-      project:      @project,
-      project_wiki: @project_wiki,
-      ref:          @ref
-    )
-
-    user = current_user if defined?(current_user)
-
-    html = if method == :gfm
-             Gitlab::Markdown.gfm(text, options)
-           else
-             Gitlab::Markdown.render(text, options)
-           end
-
-    Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user)
-  end
 end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index b0b536d4649aef959d25a6e9e8e5d330eb18e110..f3fddef01cb8c0e7226779ebaebe5f66edf21eda 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -6,7 +6,7 @@
 #
 # For example instead of this:
 #
-#   namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request)
+#   namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
 #
 # We can simply use shortcut:
 #
diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb
index 1e372d5631d78377ecfda7cab4d9b5b16ca42765..c2ab80f2e0d4f4f0b49115a9627dd48fa5d737ba 100644
--- a/app/helpers/graph_helper.rb
+++ b/app/helpers/graph_helper.rb
@@ -16,4 +16,14 @@ module GraphHelper
     ids = parents.map { |p| p.id }
     ids.zip(parent_spaces)
   end
+
+  def success_ratio(success_builds, failed_builds)
+    failed_builds = failed_builds.count(:all)
+    success_builds = success_builds.count(:all)
+
+    return 100 if failed_builds.zero?
+
+    ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100
+    ratio.to_i
+  end
 end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 1cf5b96481a952ba4d35108bd4c5ac8a2b66f0a5..5724d3aabecd861796d0d81a164822f79cddd8af 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -27,16 +27,20 @@ module IconsHelper
     end
   end
 
-  def public_icon
-    icon('globe fw')
-  end
-
-  def internal_icon
-    icon('shield fw')
-  end
+  def visibility_level_icon(level, fw: true)
+    name =
+      case level
+      when Gitlab::VisibilityLevel::PRIVATE
+        'lock'
+      when Gitlab::VisibilityLevel::INTERNAL
+        'shield'
+      else # Gitlab::VisibilityLevel::PUBLIC
+        'globe'
+      end
+      
+    name << " fw" if fw
 
-  def private_icon
-    icon('lock fw')
+    icon(name)
   end
 
   def file_type_icon_class(type, mode, name)
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 493f370d9a9bc1b33c76e57e5fd9e3e1f199d140..80e2741b09a07795b4678a3346859b0fbbe07495 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -44,28 +44,35 @@ module IssuesHelper
   end
 
   def bulk_update_milestone_options
-    options_for_select([['None (backlog)', -1]]) +
-        options_from_collection_for_select(project_active_milestones, 'id',
-                                           'title', params[:milestone_id])
+    milestones = project_active_milestones.to_a
+    milestones.unshift(Milestone::None)
+
+    options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id])
   end
 
   def milestone_options(object)
-    options_from_collection_for_select(object.project.milestones.active,
-                                       'id', 'title', object.milestone_id)
+    milestones = object.project.milestones.active.to_a
+    milestones.unshift(Milestone::None)
+
+    options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
   end
 
-  def issue_box_class(item)
+  def status_box_class(item)
     if item.respond_to?(:expired?) && item.expired?
-      'issue-box-expired'
+      'status-box-expired'
     elsif item.respond_to?(:merged?) && item.merged?
-      'issue-box-merged'
+      'status-box-merged'
     elsif item.closed?
-      'issue-box-closed'
+      'status-box-closed'
     else
-      'issue-box-open'
+      'status-box-open'
     end
   end
 
+  def issue_button_visibility(issue, closed)    
+    return 'hidden' if issue.closed? == closed
+  end
+
   def issue_to_atom(xml, issue)
     xml.entry do
       xml.id      namespace_project_issue_url(issue.project.namespace,
@@ -84,28 +91,31 @@ module IssuesHelper
   end
 
   def merge_requests_sentence(merge_requests)
-    merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
+    # Sorting based on the `!123` or `group/project!123` reference will sort
+    # local merge requests first.
+    merge_requests.map do |merge_request|
+      merge_request.to_reference(@project)
+    end.sort.to_sentence(last_word_connector: ', or ')
   end
 
-  def url_to_emoji(name)
-    emoji_path = ::AwardEmoji.path_to_emoji_image(name)
-    url_to_image(emoji_path)
-  rescue StandardError
-    ""
+  def emoji_icon(name, unicode = nil, aliases = [])
+    unicode ||= Emoji.emoji_filename(name)
+
+    content_tag :div, "",
+      class: "icon emoji-icon emoji-#{unicode}",
+      "data-emoji" => name,
+      "data-aliases" => aliases.join(" "),
+      "data-unicode-name" => unicode
   end
 
   def emoji_author_list(notes, current_user)
     list = notes.map do |note|
-             note.author == current_user ? "me" : note.author.username
+             note.author == current_user ? "me" : note.author.name
            end
 
     list.join(", ")
   end
 
-  def emoji_list
-    ::AwardEmoji::EMOJI_LIST
-  end
-
   def note_active_class(notes, current_user)
     if current_user && notes.pluck(:author_id).include?(current_user.id)
       "active"
@@ -114,6 +124,18 @@ module IssuesHelper
     end
   end
 
-  # Required for Gitlab::Markdown::IssueReferenceFilter
+  def awards_sort(awards)
+    awards.sort_by do |award, notes|
+      if award == "thumbsup"
+        0
+      elsif award == "thumbsdown"
+        1
+      else
+        2
+      end
+    end.to_h
+  end
+
+  # Required for Banzai::Filter::IssueReferenceFilter
   module_function :url_for_issue
 end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 795fb439f256ba5bba14acda80f938fbd61b91c7..a2c3d4d2f327d2aba74684daba41300b5f8c233a 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -107,6 +107,6 @@ module LabelsHelper
     options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name])
   end
 
-  # Required for Gitlab::Markdown::LabelReferenceFilter
+  # Required for Banzai::Filter::LabelReferenceFilter
   module_function :render_colored_label, :text_color_for_bg, :escape_once
 end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index b804d4f4e3b684c6e6bb901b944899f01fbdb20e..1dd07a2a2204b0368671f57e3b89b05b82432477 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -27,7 +27,16 @@ module MergeRequestsHelper
   end
 
   def ci_build_details_path(merge_request)
-    merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch)
+    build_url = merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch)
+    return nil unless build_url
+
+    parsed_url = URI.parse(build_url)
+
+    unless parsed_url.userinfo.blank?
+      parsed_url.userinfo = ''
+    end
+
+    parsed_url.to_s
   end
 
   def merge_path_description(merge_request, separator)
@@ -39,7 +48,11 @@ module MergeRequestsHelper
   end
 
   def issues_sentence(issues)
-    issues.map(&:to_reference).to_sentence
+    # Sorting based on the `#123` or `group/project#123` reference will sort
+    # local issues first.
+    issues.map do |issue|
+      issue.to_reference(@project)
+    end.sort.to_sentence
   end
 
   def mr_change_branches_path(merge_request)
@@ -49,18 +62,21 @@ module MergeRequestsHelper
         source_project_id: @merge_request.source_project_id,
         target_project_id: @merge_request.target_project_id,
         source_branch: @merge_request.source_branch,
-        target_branch: nil
-      }
+        target_branch: @merge_request.target_branch,
+      },
+      change_branches: true
     )
   end
 
   def source_branch_with_namespace(merge_request)
+    branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch))
+
     if merge_request.for_fork?
       namespace = link_to(merge_request.source_project_namespace,
         project_path(merge_request.source_project))
-      namespace + ":#{merge_request.source_branch}"
+      namespace + ":" + branch
     else
-      merge_request.source_branch
+      branch
     end
   end
 
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index ad43892b6399cc14e1312afd52f34cbefa1654e9..a42cbcff1829b12b5215eb595e0b3af2d1c59dbc 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -28,7 +28,9 @@ module MilestonesHelper
         Milestone.where(project_id: @projects)
       end.active
 
+    epoch = DateTime.parse('1970-01-01')
     grouped_milestones = GlobalMilestone.build_collection(milestones)
+    grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
     grouped_milestones.unshift(Milestone::None)
     grouped_milestones.unshift(Milestone::Any)
 
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index e7f3cb21038335c2832ec9bffac8246a48bad501..faba418c4db259614926d356d47dcfca51102b7e 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -1,10 +1,10 @@
 module NamespacesHelper
-  def namespaces_options(selected = :current_user, scope = :default)
+  def namespaces_options(selected = :current_user, display_path: false)
     groups = current_user.owned_groups + current_user.masters_groups
     users = [current_user.namespace]
 
-    group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ]
-    users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [u.human_name, u.id]} ]
+    group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [display_path ? g.path : g.human_name, g.id]} ]
+    users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [display_path ? u.path : u.human_name, u.id]} ]
 
     options = []
     options << group_opts
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 9b1dd8b8e54f79be159afbdb79ad54d6043af5ea..e6fb8670e5785a49911b9dd69351c4be28b96d83 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -4,6 +4,14 @@ module NavHelper
   end
 
   def nav_sidebar_class
+    if nav_menu_collapsed?
+      "sidebar-collapsed"
+    else
+      "sidebar-expanded"
+    end
+  end
+
+  def page_sidebar_class
     if nav_menu_collapsed?
       "page-sidebar-collapsed"
     else
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 775cf5a3dd4d2dc119bffc629a4c4f988a4605b4..791cb9e50bd013b738d5d59c22a17ea098a6816b 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -4,7 +4,82 @@ module PageLayoutHelper
 
     @page_title.push(*titles.compact) if titles.any?
 
-    @page_title.join(" | ")
+    # Segments are seperated by middot
+    @page_title.join(" \u00b7 ")
+  end
+
+  # Define or get a description for the current page
+  #
+  # description - String (default: nil)
+  #
+  # If this helper is called multiple times with an argument, only the last
+  # description will be returned when called without an argument. Descriptions
+  # have newlines replaced with spaces and all HTML tags are sanitized.
+  #
+  # Examples:
+  #
+  #   page_description # => "GitLab Community Edition"
+  #   page_description("Foo")
+  #   page_description # => "Foo"
+  #
+  #   page_description("<b>Bar</b>\nBaz")
+  #   page_description # => "Bar Baz"
+  #
+  # Returns an HTML-safe String.
+  def page_description(description = nil)
+    @page_description ||= page_description_default
+
+    if description.present?
+      @page_description = description.squish
+    else
+      sanitize(@page_description, tags: []).truncate_words(30)
+    end
+  end
+
+  # Default value for page_description when one hasn't been defined manually by
+  # a view
+  def page_description_default
+    if @project
+      @project.description || brand_title
+    else
+      brand_title
+    end
+  end
+
+  def page_image
+    default = image_url('gitlab_logo.png')
+
+    if @project
+      @project.avatar_url || default
+    elsif @user
+      avatar_icon(@user)
+    else
+      default
+    end
+  end
+
+  # Define or get attributes to be used as Twitter card metadata
+  #
+  # map - Hash of label => data pairs. Keys become labels, values become data
+  #
+  # Raises ArgumentError if given more than two attributes
+  def page_card_attributes(map = {})
+    raise ArgumentError, 'cannot provide more than two attributes' if map.length > 2
+
+    @page_card_attributes ||= {}
+    @page_card_attributes = map.reject { |_,v| v.blank? } if map.present?
+    @page_card_attributes
+  end
+
+  def page_card_meta_tags
+    tags = ''
+
+    page_card_attributes.each_with_index do |pair, i|
+      tags << tag(:meta, property: "twitter:label#{i + 1}", content: pair[0])
+      tags << tag(:meta, property: "twitter:data#{i + 1}",  content: pair[1])
+    end
+
+    tags.html_safe
   end
 
   def header_title(title = nil, title_url = nil)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index c9cd4a0d54cbeca002277f7912ac73b41a6d484e..77ba612548a11785dbb214bbd2e8026e9c879946 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -21,7 +21,7 @@ module ProjectsHelper
   end
 
   def link_to_member(project, author, opts = {})
-    default_opts = { avatar: true, name: true, size: 16, author_class: 'author' }
+    default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
     opts = default_opts.merge(opts)
 
     return "(deleted)" unless author
@@ -39,7 +39,8 @@ module ProjectsHelper
     if opts[:name]
       link_to(author_html, user_path(author), class: "author_link").html_safe
     else
-      link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => sanitize(author.name) } ).html_safe
+      title = opts[:title].sub(":name", sanitize(author.name))
+      link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => title, container: 'body' } ).html_safe
     end
   end
 
@@ -104,6 +105,14 @@ module ProjectsHelper
     end
   end
 
+  def user_max_access_in_project(user_id, project)
+    level = project.team.max_member_access(user_id)
+
+    if level
+      Gitlab::Access.options_with_owner.key(level)
+    end
+  end
+
   private
 
   def get_project_nav_tabs(project, current_user)
@@ -173,13 +182,20 @@ module ProjectsHelper
     'unknown'
   end
 
-  def default_url_to_repo(project = nil)
-    project = project || @project
-    current_user ? project.url_to_repo : project.http_url_to_repo
+  def default_url_to_repo(project = @project)
+    if default_clone_protocol == "ssh"
+      project.ssh_url_to_repo
+    else
+      project.http_url_to_repo
+    end
   end
 
   def default_clone_protocol
-    current_user ? "ssh" : "http"
+    if !current_user || current_user.require_ssh_key?
+      "http"
+    else
+      "ssh"
+    end
   end
 
   def project_last_activity(project)
@@ -269,14 +285,6 @@ module ProjectsHelper
     end
   end
 
-  def user_max_access_in_project(user, project)
-    level = project.team.max_member_access(user)
-
-    if level
-      Gitlab::Access.options_with_owner.key(level)
-    end
-  end
-
   def leave_project_message(project)
     "Are you sure you want to leave \"#{project.name}\" project?"
   end
@@ -322,10 +330,9 @@ module ProjectsHelper
   def filename_path(project, filename)
     if project && blob = project.repository.send(filename)
       namespace_project_blob_path(
-          project.namespace,
-          project,
-          tree_join(project.default_branch,
-                    blob.name)
+        project.namespace,
+        project,
+        tree_join(project.default_branch, blob.name)
       )
     end
   end
diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb
index 46eb82a354ff543ff0e8179f3c2bd561a4fa2424..9fb42487a75e6a2ddcc7d44c04ce29d14f00bf0c 100644
--- a/app/helpers/runners_helper.rb
+++ b/app/helpers/runners_helper.rb
@@ -19,7 +19,7 @@ module RunnersHelper
     id = "\##{runner.id}"
 
     if current_user && current_user.admin
-      link_to ci_admin_runner_path(runner) do
+      link_to admin_runner_path(runner) do
         display_name + id
       end
     else
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 7e54d4d1b5b9dd3d9880b1f5f6607b0d5ec59988..05386d790ca887ba4c465b5f0ebcd846d7245c35 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -15,12 +15,14 @@ module SelectsHelper
 
     html = {
       class: css_class,
-      'data-placeholder' => placeholder,
-      'data-null-user' => null_user,
-      'data-any-user' => any_user,
-      'data-email-user' => email_user,
-      'data-first-user' => first_user,
-      'data-current-user' => current_user
+      data: {
+        placeholder: placeholder,
+        null_user: null_user,
+        any_user: any_user,
+        email_user: email_user,
+        first_user: first_user,
+        current_user: current_user
+      }
     }
 
     unless opts[:scope] == :all
@@ -46,6 +48,19 @@ module SelectsHelper
     select2_tag(id, opts)
   end
 
+  def project_select_tag(id, opts = {})
+    opts[:class] ||= ''
+    opts[:class] << ' ajax-project-select'
+
+    unless opts.delete(:scope) == :all
+      if @group
+        opts['data-group-id'] = @group.id
+      end
+    end
+
+    hidden_field_tag(id, opts[:selected], opts)
+  end
+
   def select2_tag(id, opts = {})
     css_class = ''
     css_class << 'multiselect ' if opts[:multiple]
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 03a49e119b8441210e89c526f72a8fbdc46342cf..2ad7c80dae0fe5379a9f074f3fcdf6a816e128f5 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -46,12 +46,51 @@ module TreeHelper
     File.join(*args)
   end
 
-  def allowed_tree_edit?(project = nil, ref = nil)
+  def on_top_of_branch?(project = @project, ref = @ref)
+    project.repository.branch_names.include?(ref)
+  end
+
+  def can_edit_tree?(project = nil, ref = nil)
     project ||= @project
     ref ||= @ref
-    return false unless project.repository.branch_names.include?(ref)
 
-    ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
+    return false unless on_top_of_branch?(project, ref)
+
+    can?(current_user, :push_code, project) ||
+      (current_user && current_user.already_forked?(project))
+  end
+
+  def tree_edit_branch(project = @project, ref = @ref)
+    return unless can_edit_tree?(project, ref)
+
+    if can_push_branch?(project, ref)
+      ref
+    else
+      project = tree_edit_project(project)
+      project.repository.next_patch_branch
+    end
+  end
+
+  def tree_edit_project(project = @project)
+    if can?(current_user, :push_code, project)
+      project
+    elsif current_user && current_user.already_forked?(project)
+      current_user.fork_of(project)
+    end
+  end
+
+  def edit_in_new_fork_notice_now
+    "You're not allowed to make changes to this project directly." +
+      " A fork of this project is being created that you can make changes in, so you can submit a merge request."
+  end
+
+  def edit_in_new_fork_notice
+    "You're not allowed to make changes to this project directly." +
+      " A fork of this project has been created that you can make changes in, so you can submit a merge request."
+  end
+
+  def commit_in_fork_help
+    "A new branch will be created in your fork and a new merge request will be started."
   end
 
   def tree_breadcrumbs(tree, max_links = 2)
@@ -65,7 +104,7 @@ module TreeHelper
         part_path = File.join(part_path, part) unless part_path.empty?
         part_path = part if part_path.empty?
 
-        next unless parts.last(2).include?(part) if parts.count > max_links
+        next if parts.count > max_links && !parts.last(2).include?(part)
         yield(part, tree_join(@ref, part_path))
       end
     end
diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb
index 2a3a7e80fca1117da4d16020c4454447b85d41d4..8cad994d10fe2e9dba30699f5c1243c414ac679b 100644
--- a/app/helpers/triggers_helper.rb
+++ b/app/helpers/triggers_helper.rb
@@ -1,5 +1,5 @@
 module TriggersHelper
-  def ci_build_trigger_url(project_id, ref_name)
-    "#{Settings.gitlab_ci.url}/ci/api/v1/projects/#{project_id}/refs/#{ref_name}/trigger"
+  def builds_trigger_url(project_id)
+    "#{Settings.gitlab.url}/api/v3/projects/#{project_id}/trigger/builds"
   end
 end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index b52cd23aba22e378ad114ce8e8ed690d7f4d194f..71d33b445c22bea272c664e004ef0f01401ea4db 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -12,61 +12,41 @@ module VisibilityLevelHelper
 
   # Return the description for the +level+ argument.
   #
-  # +level+ One of the Gitlab::VisibilityLevel constants
-  # +form_model+ Either a model object (Project, Snippet, etc.) or the name of
-  #              a Project or Snippet class.
+  # +level+       One of the Gitlab::VisibilityLevel constants
+  # +form_model+  Either a model object (Project, Snippet, etc.) or the name of
+  #               a Project or Snippet class.
   def visibility_level_description(level, form_model)
-    case form_model.is_a?(String) ? form_model : form_model.class.name
-    when 'PersonalSnippet', 'ProjectSnippet', 'Snippet'
-      snippet_visibility_level_description(level)
-    when 'Project'
+    case form_model
+    when Project
       project_visibility_level_description(level)
+    when Snippet
+      snippet_visibility_level_description(level, form_model)
     end
   end
 
   def project_visibility_level_description(level)
-    capture_haml do
-      haml_tag :span do
-        case level
-        when Gitlab::VisibilityLevel::PRIVATE
-          haml_concat "Project access must be granted explicitly for each user."
-        when Gitlab::VisibilityLevel::INTERNAL
-          haml_concat "The project can be cloned by"
-          haml_concat "any logged in user."
-        when Gitlab::VisibilityLevel::PUBLIC
-          haml_concat "The project can be cloned"
-          haml_concat "without any"
-          haml_concat "authentication."
-        end
-      end
-    end
-  end
-
-  def snippet_visibility_level_description(level)
-    capture_haml do
-      haml_tag :span do
-        case level
-        when Gitlab::VisibilityLevel::PRIVATE
-          haml_concat "The snippet is visible only for me."
-        when Gitlab::VisibilityLevel::INTERNAL
-          haml_concat "The snippet is visible for any logged in user."
-        when Gitlab::VisibilityLevel::PUBLIC
-          haml_concat "The snippet can be accessed"
-          haml_concat "without any"
-          haml_concat "authentication."
-        end
-      end
+    case level
+    when Gitlab::VisibilityLevel::PRIVATE
+      "Project access must be granted explicitly to each user."
+    when Gitlab::VisibilityLevel::INTERNAL
+      "The project can be cloned by any logged in user."
+    when Gitlab::VisibilityLevel::PUBLIC
+      "The project can be cloned without any authentication."
     end
   end
 
-  def visibility_level_icon(level)
+  def snippet_visibility_level_description(level, snippet = nil)
     case level
     when Gitlab::VisibilityLevel::PRIVATE
-      private_icon
+      if snippet.is_a? ProjectSnippet
+        "The snippet is visible only to project members."
+      else
+        "The snippet is visible only to me."
+      end
     when Gitlab::VisibilityLevel::INTERNAL
-      internal_icon
+      "The snippet is visible to any logged in user."
     when Gitlab::VisibilityLevel::PUBLIC
-      public_icon
+      "The snippet can be accessed without any authentication."
     end
   end
 
@@ -89,7 +69,6 @@ module VisibilityLevelHelper
 
   def skip_level?(form_model, level)
     form_model.is_a?(Project) &&
-    form_model.forked? &&
-    !Gitlab::VisibilityLevel.allowed_fork_levels(form_model.forked_from_project.visibility_level).include?(level)
+    !form_model.visibility_level_allowed?(level)
   end
 end
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index aedb0889185cf27e2393d0fb6abb9e2d34bc5a03..8b83bbd93b74fc42d04bf4470f0d4d39586486e0 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -8,10 +8,6 @@ class BaseMailer < ActionMailer::Base
   default from:     Proc.new { default_sender_address.format }
   default reply_to: Proc.new { default_reply_to_address.format }
 
-  def self.delay
-    delay_for(2.seconds)
-  end
-
   def can?
     Ability.abilities.allowed?(current_user, action, subject)
   end
diff --git a/app/mailers/ci/emails/builds.rb b/app/mailers/ci/emails/builds.rb
deleted file mode 100644
index 6fb4fba85e5bb27f305c82d9699ca29d76695854..0000000000000000000000000000000000000000
--- a/app/mailers/ci/emails/builds.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module Ci
-  module Emails
-    module Builds
-      def build_fail_email(build_id, to)
-        @build = Ci::Build.find(build_id)
-        @project = @build.project
-        mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
-      end
-
-      def build_success_email(build_id, to)
-        @build = Ci::Build.find(build_id)
-        @project = @build.project
-        mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
-      end
-    end
-  end
-end
diff --git a/app/mailers/ci/notify.rb b/app/mailers/ci/notify.rb
deleted file mode 100644
index 404842cf213b4e43201986a0cc565130b3ebddff..0000000000000000000000000000000000000000
--- a/app/mailers/ci/notify.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-module Ci
-  class Notify < ActionMailer::Base
-    include Ci::Emails::Builds
-
-    add_template_helper Ci::GitlabHelper
-
-    default_url_options[:host]     = Gitlab.config.gitlab.host
-    default_url_options[:protocol] = Gitlab.config.gitlab.protocol
-    default_url_options[:port]     = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
-    default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
-
-    default from: Gitlab.config.gitlab.email_from
-
-    # Just send email with 3 seconds delay
-    def self.delay
-      delay_for(2.seconds)
-    end
-
-    private
-
-    # Formats arguments into a String suitable for use as an email subject
-    #
-    # extra - Extra Strings to be inserted into the subject
-    #
-    # Examples
-    #
-    #   >> subject('Lorem ipsum')
-    #   => "GitLab-CI | Lorem ipsum"
-    #
-    #   # Automatically inserts Project name when @project is set
-    #   >> @project = Project.last
-    #   => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
-    #   >> subject('Lorem ipsum')
-    #   => "GitLab-CI | Ruby on Rails | Lorem ipsum "
-    #
-    #   # Accepts multiple arguments
-    #   >> subject('Lorem ipsum', 'Dolor sit amet')
-    #   => "GitLab-CI | Lorem ipsum | Dolor sit amet"
-    def subject(*extra)
-      subject = "GitLab-CI"
-      subject << (@project ? " | #{@project.name}" : "")
-      subject << " | " + extra.join(' | ') if extra.present?
-      subject
-    end
-  end
-end
diff --git a/app/mailers/emails/builds.rb b/app/mailers/emails/builds.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d58609a2de5cca5e61c92754b33562e3735ab683
--- /dev/null
+++ b/app/mailers/emails/builds.rb
@@ -0,0 +1,15 @@
+module Emails
+  module Builds
+    def build_fail_email(build_id, to)
+      @build = Ci::Build.find(build_id)
+      @project = @build.project
+      mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
+    end
+
+    def build_success_email(build_id, to)
+      @build = Ci::Build.find(build_id)
+      @project = @build.project
+      mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
+    end
+  end
+end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index caba63006da093192fe24305d6a0efb741244372..b96418679bd756ef0a64e7a0b3933b9a6750df3c 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -59,85 +59,17 @@ module Emails
            subject: subject("Project was moved"))
     end
 
-    def repository_push_email(project_id, recipient,  author_id: nil, 
-                                                      ref: nil, 
-                                                      action: nil, 
-                                                      compare: nil, 
-                                                      reverse_compare: false, 
-                                                      send_from_committer_email: false, 
-                                                      disable_diffs: false)
-      unless author_id && ref && action
-        raise ArgumentError, "missing keywords: author_id, ref, action"
-      end
+    def repository_push_email(project_id, recipient, opts = {})
+      @message =
+        Gitlab::Email::Message::RepositoryPush.new(self, project_id, recipient, opts)
 
-      @project = Project.find(project_id)
-      @current_user = @author  = User.find(author_id)
-      @reverse_compare = reverse_compare
-      @compare = compare
-      @ref_name  = Gitlab::Git.ref_name(ref)
-      @ref_type  = Gitlab::Git.tag_ref?(ref) ? "tag" : "branch"
-      @action  = action
-      @disable_diffs = disable_diffs
-
-      if @compare
-        @commits = Commit.decorate(compare.commits, @project)
-        @diffs   = compare.diffs
-      end
-
-      @action_name = 
-        case action
-        when :create
-          "pushed new"
-        when :delete
-          "deleted"
-        else
-          "pushed to"
-        end
-
-      @subject = "[Git]"
-      @subject << "[#{@project.path_with_namespace}]"
-      @subject << "[#{@ref_name}]" if action == :push
-      @subject << " "
-
-      if action == :push
-        if @commits.length > 1
-          @target_url = namespace_project_compare_url(@project.namespace,
-                                                      @project,
-                                                      from: Commit.new(@compare.base, @project),
-                                                      to:   Commit.new(@compare.head, @project))
-          @subject << "Deleted " if @reverse_compare
-          @subject << "#{@commits.length} commits: #{@commits.first.title}"
-        else
-          @target_url = namespace_project_commit_url(@project.namespace,
-                                                     @project, @commits.first)
-
-          @subject << "Deleted 1 commit: " if @reverse_compare
-          @subject << @commits.first.title
-        end
-      else
-        unless action == :delete
-          @target_url = namespace_project_tree_url(@project.namespace,
-                                                   @project, @ref_name)
-        end
-
-        subject_action = @action_name.dup
-        subject_action[0] = subject_action[0].capitalize
-        @subject << "#{subject_action} #{@ref_type} #{@ref_name}"
-      end
-
-      @disable_footer = true
-
-      reply_to = 
-        if send_from_committer_email && can_send_from_user_email?(@author)
-          @author.email
-        else
-          Gitlab.config.gitlab.email_reply_to
-        end
-
-      mail(from:      sender(author_id, send_from_committer_email),
-           reply_to:  reply_to,
-           to:        recipient,
-           subject:   @subject)
+      # used in notify layout
+      @target_url = @message.target_url
+
+      mail(from:      sender(@message.author_id, @message.send_from_committer_email?),
+           reply_to:  @message.reply_to,
+           to:        @message.recipient,
+           subject:   @message.subject)
     end
   end
 end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 50a409c375477574aba1804220d651d0f3d2e570..3bbdd9cee76c2761718bc65259cbebfd5f8c65bf 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -7,6 +7,7 @@ class Notify < BaseMailer
   include Emails::Projects
   include Emails::Profile
   include Emails::Groups
+  include Emails::Builds
 
   add_template_helper MergeRequestsHelper
   add_template_helper EmailsHelper
@@ -16,7 +17,7 @@ class Notify < BaseMailer
          subject: subject,
          body: body.html_safe,
          content_type: 'text/html'
-    )
+        )
   end
 
   # Splits "gitlab.corp.company.com" up into "gitlab.corp.company.com",
@@ -33,13 +34,13 @@ class Notify < BaseMailer
     allowed_domains
   end
 
-  private
-
   def can_send_from_user_email?(sender)
     sender_domain = sender.email.split("@").last
     self.class.allowed_email_domains.include?(sender_domain)
   end
 
+  private
+
   # Return an email address that displays the name of the sender.
   # Only the displayed name changes; the actual email address is always the same.
   def sender(sender_id, send_from_user_email = false)
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 07f3a56ec7abd8c8d245d33c398e2c32475efc54..1b3ee757040515250134b79afe752fc30883f16a 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -132,14 +132,14 @@ class Ability
     end
 
     def public_project_rules
-      project_guest_rules + [
+      @public_project_rules ||= project_guest_rules + [
         :download_code,
         :fork_project
       ]
     end
 
     def project_guest_rules
-      [
+      @project_guest_rules ||= [
         :read_project,
         :read_wiki,
         :read_issue,
@@ -157,7 +157,7 @@ class Ability
     end
 
     def project_report_rules
-      project_guest_rules + [
+      @project_report_rules ||= project_guest_rules + [
         :create_commit_status,
         :read_commit_statuses,
         :download_code,
@@ -170,7 +170,7 @@ class Ability
     end
 
     def project_dev_rules
-      project_report_rules + [
+      @project_dev_rules ||= project_report_rules + [
         :admin_merge_request,
         :create_merge_request,
         :create_wiki,
@@ -181,7 +181,7 @@ class Ability
     end
 
     def project_archived_rules
-      [
+      @project_archived_rules ||= [
         :create_merge_request,
         :push_code,
         :push_code_to_protected_branches,
@@ -191,7 +191,7 @@ class Ability
     end
 
     def project_master_rules
-      project_dev_rules + [
+      @project_master_rules ||= project_dev_rules + [
         :push_code_to_protected_branches,
         :update_project_snippet,
         :update_merge_request,
@@ -206,7 +206,7 @@ class Ability
     end
 
     def project_admin_rules
-      project_master_rules + [
+      @project_admin_rules ||= project_master_rules + [
         :change_namespace,
         :change_visibility_level,
         :rename_project,
@@ -332,7 +332,7 @@ class Ability
       end
 
       if snippet.public? || snippet.internal?
-        rules << :read_personal_snippet 
+        rules << :read_personal_snippet
       end
 
       rules
@@ -346,12 +346,10 @@ class Ability
       unless group.last_owner?(target_user)
         can_manage = group_abilities(user, group).include?(:admin_group_member)
 
-        if can_manage && user != target_user
+        if can_manage
           rules << :update_group_member
           rules << :destroy_group_member
-        end
-
-        if user == target_user
+        elsif user == target_user
           rules << :destroy_group_member
         end
       end
@@ -367,12 +365,10 @@ class Ability
       unless target_user == project.owner
         can_manage = project_abilities(user, project).include?(:admin_project_member)
 
-        if can_manage && user != target_user
+        if can_manage
           rules << :update_project_member
           rules << :destroy_project_member
-        end
-
-        if user == target_user
+        elsif user == target_user
           rules << :destroy_project_member
         end
       end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 9e70247ef517dfaf5f944e0aad0d2ba79917ddeb..be69d317d7306edd3577c51e6a95f5600e503fdb 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -2,55 +2,74 @@
 #
 # Table name: application_settings
 #
-#  id                           :integer          not null, primary key
-#  default_projects_limit       :integer
-#  signup_enabled               :boolean
-#  signin_enabled               :boolean
-#  gravatar_enabled             :boolean
-#  sign_in_text                 :text
-#  created_at                   :datetime
-#  updated_at                   :datetime
-#  home_page_url                :string(255)
-#  default_branch_protection    :integer          default(2)
-#  twitter_sharing_enabled      :boolean          default(TRUE)
-#  restricted_visibility_levels :text
-#  version_check_enabled        :boolean          default(TRUE)
-#  max_attachment_size          :integer          default(10), not null
-#  default_project_visibility   :integer
-#  default_snippet_visibility   :integer
-#  restricted_signup_domains    :text
-#  user_oauth_applications      :boolean          default(TRUE)
-#  after_sign_out_path          :string(255)
-#  session_expire_delay         :integer          default(10080), not null
-#  import_sources               :text
-#  help_page_text               :text
-#  admin_notification_email     :string(255)
-#  shared_runners_enabled       :boolean          default(TRUE), not null
-#  max_artifacts_size           :integer          default(100), not null
+#  id                                :integer          not null, primary key
+#  default_projects_limit            :integer
+#  signup_enabled                    :boolean
+#  signin_enabled                    :boolean
+#  gravatar_enabled                  :boolean
+#  sign_in_text                      :text
+#  created_at                        :datetime
+#  updated_at                        :datetime
+#  home_page_url                     :string(255)
+#  default_branch_protection         :integer          default(2)
+#  twitter_sharing_enabled           :boolean          default(TRUE)
+#  restricted_visibility_levels      :text
+#  version_check_enabled             :boolean          default(TRUE)
+#  max_attachment_size               :integer          default(10), not null
+#  default_project_visibility        :integer
+#  default_snippet_visibility        :integer
+#  restricted_signup_domains         :text
+#  user_oauth_applications           :boolean          default(TRUE)
+#  after_sign_out_path               :string(255)
+#  session_expire_delay              :integer          default(10080), not null
+#  import_sources                    :text
+#  help_page_text                    :text
+#  admin_notification_email          :string(255)
+#  shared_runners_enabled            :boolean          default(TRUE), not null
+#  max_artifacts_size                :integer          default(100), not null
+#  runners_registration_token        :string(255)
+#  require_two_factor_authentication :boolean          default(TRUE)
+#  two_factor_grace_period           :integer          default(48)
 #
 
 class ApplicationSetting < ActiveRecord::Base
+  include TokenAuthenticatable
+  add_authentication_token_field :runners_registration_token
+
+  CACHE_KEY = 'application_setting.last'
+
   serialize :restricted_visibility_levels
   serialize :import_sources
   serialize :restricted_signup_domains, Array
   attr_accessor :restricted_signup_domains_raw
 
   validates :session_expire_delay,
-    presence: true,
-    numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+            presence: true,
+            numericality: { only_integer: true, greater_than_or_equal_to: 0 }
 
   validates :home_page_url,
-    allow_blank: true,
-    format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" },
-    if: :home_page_url_column_exist
+            allow_blank: true,
+            url: true,
+            if: :home_page_url_column_exist
 
   validates :after_sign_out_path,
-    allow_blank: true,
-    format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
+            allow_blank: true,
+            url: true
 
   validates :admin_notification_email,
-    allow_blank: true,
-    email: true
+            allow_blank: true,
+            email: true
+
+  validates :two_factor_grace_period,
+            numericality: { greater_than_or_equal_to: 0 }
+
+  validates :recaptcha_site_key,
+            presence: true,
+            if: :recaptcha_enabled
+
+  validates :recaptcha_private_key,
+            presence: true,
+            if: :recaptcha_enabled
 
   validates_each :restricted_visibility_levels do |record, attr, value|
     unless value.nil?
@@ -72,16 +91,22 @@ class ApplicationSetting < ActiveRecord::Base
     end
   end
 
+  before_save :ensure_runners_registration_token
+
   after_commit do
-    Rails.cache.write('application_setting.last', self)
+    Rails.cache.write(CACHE_KEY, self)
   end
 
   def self.current
-    Rails.cache.fetch('application_setting.last') do
+    Rails.cache.fetch(CACHE_KEY) do
       ApplicationSetting.last
     end
   end
 
+  def self.expire
+    Rails.cache.delete(CACHE_KEY)
+  end
+
   def self.create_from_defaults
     create(
       default_projects_limit: Settings.gitlab['default_projects_limit'],
@@ -99,7 +124,9 @@ class ApplicationSetting < ActiveRecord::Base
       restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
       import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'],
       shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
-      max_artifacts_size: Settings.gitlab_ci['max_artifacts_size'],
+      max_artifacts_size: Settings.artifacts['max_size'],
+      require_two_factor_authentication: false,
+      two_factor_grace_period: 48
     )
   end
 
@@ -114,13 +141,16 @@ class ApplicationSetting < ActiveRecord::Base
   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)
+      /\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? }
   end
 
+  def runners_registration_token
+    ensure_runners_registration_token!
+  end
 end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 05f5e9796959d577298a87dd0bc366f24d686865..ad514706160bd49b15f20d3226943b738ee11cee 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -16,12 +16,12 @@
 class BroadcastMessage < ActiveRecord::Base
   include Sortable
 
-  validates :message, presence: true
+  validates :message,   presence: true
   validates :starts_at, presence: true
-  validates :ends_at, presence: true
+  validates :ends_at,   presence: true
 
-  validates :color, format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true
-  validates :font,  format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true
+  validates :color, allow_blank: true, color: true
+  validates :font,  allow_blank: true, color: true
 
   def self.current
     where("ends_at > :now AND starts_at < :now", now: Time.zone.now).last
diff --git a/app/models/ci/application_setting.rb b/app/models/ci/application_setting.rb
deleted file mode 100644
index 1307fa0b472def2dd758f3c15ee50c47dd43a473..0000000000000000000000000000000000000000
--- a/app/models/ci/application_setting.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_application_settings
-#
-#  id                :integer          not null, primary key
-#  all_broken_builds :boolean
-#  add_pusher        :boolean
-#  created_at        :datetime
-#  updated_at        :datetime
-#
-
-module Ci
-  class ApplicationSetting < ActiveRecord::Base
-    extend Ci::Model
-
-    after_commit do
-      Rails.cache.write('ci_application_setting.last', self)
-    end
-
-    def self.current
-      Rails.cache.fetch('ci_application_setting.last') do
-        Ci::ApplicationSetting.last
-      end
-    end
-
-    def self.create_from_defaults
-      create(
-        all_broken_builds: Settings.gitlab_ci['all_broken_builds'],
-        add_pusher: Settings.gitlab_ci['add_pusher'],
-      )
-    end
-  end
-end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index e78b154084b014cc4c773b7f42b9a12c4c163624..7b89fe069ea158061ee29c98d3f0c2fcc2e13c84 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -84,6 +84,7 @@ module Ci
         new_build.options = build.options
         new_build.commands = build.commands
         new_build.tag_list = build.tag_list
+        new_build.gl_project_id = build.gl_project_id
         new_build.commit_id = build.commit_id
         new_build.name = build.name
         new_build.allow_failure = build.allow_failure
@@ -96,19 +97,16 @@ module Ci
     end
 
     state_machine :status, initial: :pending do
-      after_transition any => [:success, :failed, :canceled] do |build, transition|
-        project = build.project
+      after_transition pending: :running do |build, transition|
+        build.execute_hooks
+      end
 
-        if project.web_hooks?
-          Ci::WebHookService.new.build_end(build)
-        end
+      after_transition any => [:success, :failed, :canceled] do |build, transition|
+        return unless build.project
 
+        build.update_coverage
         build.commit.create_next_builds(build)
-        project.execute_services(build)
-
-        if project.coverage_enabled?
-          build.update_coverage
-        end
+        build.execute_hooks
       end
     end
 
@@ -117,7 +115,7 @@ module Ci
     end
 
     def retryable?
-      commands.present?
+      project.builds_enabled? && commands.present?
     end
 
     def retried?
@@ -130,13 +128,23 @@ module Ci
     end
 
     def timeout
-      project.timeout
+      project.build_timeout
     end
 
     def variables
       predefined_variables + yaml_variables + project_variables + trigger_variables
     end
 
+    def merge_request
+      merge_requests = MergeRequest.includes(:merge_request_diff)
+                                   .where(source_branch: ref, source_project_id: commit.gl_project_id)
+                                   .reorder(iid: :asc)
+
+      merge_requests.find do |merge_request|
+        merge_request.commits.any? { |ci| ci.id == commit.sha }
+      end
+    end
+
     def project
       commit.project
     end
@@ -149,26 +157,21 @@ module Ci
       project.name
     end
 
-    def project_recipients
-      recipients = project.email_recipients.split(' ')
-
-      if project.email_add_pusher? && user.present? && user.notification_email.present?
-        recipients << user.notification_email
-      end
-
-      recipients.uniq
-    end
-
     def repo_url
-      project.repo_url_with_auth
+      auth = "gitlab-ci-token:#{token}@"
+      project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
+        prefix + auth
+      end
     end
 
     def allow_git_fetch
-      project.allow_git_fetch
+      project.build_allow_git_fetch
     end
 
     def update_coverage
-      coverage = extract_coverage(trace, project.coverage_regex)
+      coverage_regex = project.build_coverage_regex
+      return unless coverage_regex
+      coverage = extract_coverage(trace, coverage_regex)
 
       if coverage.is_a? Numeric
         update_attributes(coverage: coverage)
@@ -177,7 +180,8 @@ module Ci
 
     def extract_coverage(text, regex)
       begin
-        matches = text.gsub(Regexp.new(regex)).to_a.last
+        matches = text.scan(Regexp.new(regex)).last
+        matches = matches.last if matches.kind_of?(Array)
         coverage = matches.gsub(/\d+(\.\d+)?/).first
 
         if coverage.present?
@@ -201,7 +205,7 @@ module Ci
     def trace
       trace = raw_trace
       if project && trace.present?
-        trace.gsub(project.token, 'xxxxxx')
+        trace.gsub(project.runners_token, 'xxxxxx')
       else
         trace
       end
@@ -228,29 +232,29 @@ module Ci
     end
 
     def token
-      project.token
+      project.runners_token
     end
 
     def valid_token? token
-      project.valid_token? token
+      project.valid_runners_token? token
     end
 
     def target_url
       Gitlab::Application.routes.url_helpers.
-        namespace_project_build_url(gl_project.namespace, gl_project, self)
+        namespace_project_build_url(project.namespace, project, self)
     end
 
     def cancel_url
       if active?
         Gitlab::Application.routes.url_helpers.
-          cancel_namespace_project_build_path(gl_project.namespace, gl_project, self)
+          cancel_namespace_project_build_path(project.namespace, project, self)
       end
     end
 
     def retry_url
       if retryable?
         Gitlab::Application.routes.url_helpers.
-          retry_namespace_project_build_path(gl_project.namespace, gl_project, self)
+          retry_namespace_project_build_path(project.namespace, project, self)
       end
     end
 
@@ -269,10 +273,18 @@ module Ci
     def download_url
       if artifacts_file.exists?
         Gitlab::Application.routes.url_helpers.
-          download_namespace_project_build_path(gl_project.namespace, gl_project, self)
+          download_namespace_project_build_path(project.namespace, project, self)
       end
     end
 
+    def execute_hooks
+      build_data = Gitlab::BuildDataBuilder.build(self)
+      project.execute_hooks(build_data.dup, :build_hooks)
+      project.execute_services(build_data.dup, :build_hooks)
+    end
+
+
+
     private
 
     def yaml_variables
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index 971e899de847b6c6a5043c4f37d3404e080dd628..d2a29236942eebead2e1bb85f2087b2a283e3a06 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -20,8 +20,8 @@ module Ci
   class Commit < ActiveRecord::Base
     extend Ci::Model
 
-    belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id
-    has_many :statuses, dependent: :destroy, class_name: 'CommitStatus'
+    belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
+    has_many :statuses, class_name: 'CommitStatus'
     has_many :builds, class_name: 'Ci::Build'
     has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
 
@@ -38,10 +38,6 @@ module Ci
       sha
     end
 
-    def project
-      @project ||= gl_project.ensure_gitlab_ci_project
-    end
-
     def project_id
       project.id
     end
@@ -57,7 +53,7 @@ module Ci
     end
 
     def valid_commit_sha
-      if self.sha == Ci::Git::BLANK_SHA
+      if self.sha == Gitlab::Git::BLANK_SHA
         self.errors.add(:sha, " cant be 00000000 (branch removal)")
       end
     end
@@ -79,7 +75,7 @@ module Ci
     end
 
     def commit_data
-      @commit ||= gl_project.commit(sha)
+      @commit ||= project.commit(sha)
     rescue
       nil
     end
@@ -165,21 +161,31 @@ module Ci
       status == 'canceled'
     end
 
+    def active?
+      running? || pending?
+    end
+
+    def complete?
+      canceled? || success? || failed?
+    end
+
     def duration
       duration_array = latest_statuses.map(&:duration).compact
       duration_array.reduce(:+).to_i
     end
 
+    def started_at
+      @started_at ||= statuses.order('started_at ASC').first.try(:started_at)
+    end
+
     def finished_at
       @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
     end
 
     def coverage
-      if project.coverage_enabled?
-        coverage_array = latest_builds.map(&:coverage).compact
-        if coverage_array.size >= 1
-          '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
-        end
+      coverage_array = latest_builds.map(&:coverage).compact
+      if coverage_array.size >= 1
+        '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
       end
     end
 
@@ -189,7 +195,7 @@ module Ci
 
     def config_processor
       return nil unless ci_yaml_file
-      @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, gl_project.path_with_namespace)
+      @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
     rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
       save_yaml_error(e.message)
       nil
@@ -199,7 +205,7 @@ module Ci
     end
 
     def ci_yaml_file
-      gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data
+      @ci_yaml_file ||= project.repository.blob_at(sha, '.gitlab-ci.yml').data
     rescue
       nil
     end
diff --git a/app/models/ci/event.rb b/app/models/ci/event.rb
deleted file mode 100644
index 8c39be4267767d52a6e69f2f78035a7417001fec..0000000000000000000000000000000000000000
--- a/app/models/ci/event.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_events
-#
-#  id          :integer          not null, primary key
-#  project_id  :integer
-#  user_id     :integer
-#  is_admin    :integer
-#  description :text
-#  created_at  :datetime
-#  updated_at  :datetime
-#
-
-module Ci
-  class Event < ActiveRecord::Base
-    extend Ci::Model
-    
-    belongs_to :project, class_name: 'Ci::Project'
-
-    validates :description,
-      presence: true,
-      length: { in: 5..200 }
-
-    scope :admin, ->(){ where(is_admin: true) }
-    scope :project_wide, ->(){ where(is_admin: false) }
-  end
-end
diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb
deleted file mode 100644
index 669ee1cc0d2d3b6d46a7aacee74fde6c5153a26c..0000000000000000000000000000000000000000
--- a/app/models/ci/project.rb
+++ /dev/null
@@ -1,192 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_projects
-#
-#  id                       :integer          not null, primary key
-#  name                     :string(255)
-#  timeout                  :integer          default(3600), not null
-#  created_at               :datetime
-#  updated_at               :datetime
-#  token                    :string(255)
-#  default_ref              :string(255)
-#  path                     :string(255)
-#  always_build             :boolean          default(FALSE), not null
-#  polling_interval         :integer
-#  public                   :boolean          default(FALSE), not null
-#  ssh_url_to_repo          :string(255)
-#  gitlab_id                :integer
-#  allow_git_fetch          :boolean          default(TRUE), not null
-#  email_recipients         :string(255)      default(""), not null
-#  email_add_pusher         :boolean          default(TRUE), not null
-#  email_only_broken_builds :boolean          default(TRUE), not null
-#  skip_refs                :string(255)
-#  coverage_regex           :string(255)
-#  shared_runners_enabled   :boolean          default(FALSE)
-#  generated_yaml_config    :text
-#
-
-module Ci
-  class Project < ActiveRecord::Base
-    extend Ci::Model
-
-    include Ci::ProjectStatus
-
-    belongs_to :gl_project, class_name: '::Project', foreign_key: :gitlab_id
-
-    has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
-    has_many :runners, through: :runner_projects, class_name: 'Ci::Runner'
-    has_many :web_hooks, dependent: :destroy, class_name: 'Ci::WebHook'
-    has_many :events, dependent: :destroy, class_name: 'Ci::Event'
-    has_many :variables, dependent: :destroy, class_name: 'Ci::Variable'
-    has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger'
-
-    # Project services
-    has_many :services, dependent: :destroy, class_name: 'Ci::Service'
-    has_one :hip_chat_service, dependent: :destroy, class_name: 'Ci::HipChatService'
-    has_one :slack_service, dependent: :destroy, class_name: 'Ci::SlackService'
-    has_one :mail_service, dependent: :destroy, class_name: 'Ci::MailService'
-
-    accepts_nested_attributes_for :variables, allow_destroy: true
-
-    delegate :name_with_namespace, :path_with_namespace, :web_url, :http_url_to_repo, :ssh_url_to_repo, to: :gl_project
-
-    #
-    # Validations
-    #
-    validates_presence_of :timeout, :token, :default_ref, :gitlab_id
-
-    validates_uniqueness_of :gitlab_id
-
-    validates :polling_interval,
-              presence: true,
-              if: ->(project) { project.always_build.present? }
-
-    before_validation :set_default_values
-
-    class << self
-      include Ci::CurrentSettings
-
-      def unassigned(runner)
-        joins("LEFT JOIN #{Ci::RunnerProject.table_name} ON #{Ci::RunnerProject.table_name}.project_id = #{Ci::Project.table_name}.id " \
-          "AND #{Ci::RunnerProject.table_name}.runner_id = #{runner.id}").
-        where("#{Ci::RunnerProject.table_name}.project_id" => nil)
-      end
-
-      def ordered_by_last_commit_date
-        last_commit_subquery = "(SELECT gl_project_id, MAX(committed_at) committed_at FROM #{Ci::Commit.table_name} GROUP BY gl_project_id)"
-        joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.gitlab_id = last_commit.gl_project_id").
-          joins(:gl_project).
-          order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC")
-      end
-    end
-
-    def name
-      name_with_namespace
-    end
-
-    def path
-      path_with_namespace
-    end
-
-    def gitlab_url
-      web_url
-    end
-
-    def any_runners?(&block)
-      if runners.active.any?(&block)
-        return true
-      end
-
-      shared_runners_enabled && Ci::Runner.shared.active.any?(&block)
-    end
-
-    def set_default_values
-      self.token = SecureRandom.hex(15) if self.token.blank?
-      self.default_ref ||= 'master'
-    end
-
-    def tracked_refs
-      @tracked_refs ||= default_ref.split(",").map { |ref| ref.strip }
-    end
-
-    def valid_token? token
-      self.token && self.token == token
-    end
-
-    def no_running_builds?
-      # Get running builds not later than 3 days ago to ignore hangs
-      builds.running.where("updated_at > ?", 3.days.ago).empty?
-    end
-
-    def email_notification?
-      email_add_pusher || email_recipients.present?
-    end
-
-    def web_hooks?
-      web_hooks.any?
-    end
-
-    def services?
-      services.any?
-    end
-
-    def timeout_in_minutes
-      timeout / 60
-    end
-
-    def timeout_in_minutes=(value)
-      self.timeout = value.to_i * 60
-    end
-
-    def coverage_enabled?
-      coverage_regex.present?
-    end
-
-    # Build a clone-able repo url
-    # using http and basic auth
-    def repo_url_with_auth
-      auth = "gitlab-ci-token:#{token}@"
-      http_url_to_repo.sub(/^https?:\/\//) do |prefix|
-        prefix + auth
-      end
-    end
-
-    def available_services_names
-      %w(slack mail hip_chat)
-    end
-
-    def build_missing_services
-      available_services_names.each do |service_name|
-        service = services.find { |service| service.to_param == service_name }
-
-        # If service is available but missing in db
-        # we should create an instance. Ex `create_gitlab_ci_service`
-        self.send :"create_#{service_name}_service" if service.nil?
-      end
-    end
-
-    def execute_services(data)
-      services.each do |service|
-
-        # Call service hook only if it is active
-        begin
-          service.execute(data) if service.active && service.can_execute?(data)
-        rescue => e
-          logger.error(e)
-        end
-      end
-    end
-
-    def setup_finished?
-      commits.any?
-    end
-
-    def commits
-      gl_project.ci_commits.ordered
-    end
-
-    def builds
-      gl_project.ci_builds
-    end
-  end
-end
diff --git a/app/models/ci/project_status.rb b/app/models/ci/project_status.rb
deleted file mode 100644
index 2d35aeac2256f44d58b6e612fd6d75a04897d242..0000000000000000000000000000000000000000
--- a/app/models/ci/project_status.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-module Ci
-  module ProjectStatus
-    def status
-      last_commit.status if last_commit
-    end
-
-    def broken?
-      last_commit.failed? if last_commit
-    end
-
-    def success?
-      last_commit.success? if last_commit
-    end
-
-    def broken_or_success?
-      broken? || success?
-    end
-
-    def last_commit
-      @last_commit ||= commits.last if commits.any?
-    end
-
-    def last_commit_date
-      last_commit.try(:created_at)
-    end
-
-    def human_status
-      status
-    end
-  end
-end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 89710485811202a5b6bf3649177c0483f2bb997b..38b20cd7faa42291c860840a73d6563958434060 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -25,7 +25,7 @@ module Ci
     
     has_many :builds, class_name: 'Ci::Build'
     has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
-    has_many :projects, through: :runner_projects, class_name: 'Ci::Project'
+    has_many :projects, through: :runner_projects, class_name: '::Project', foreign_key: :gl_project_id
 
     has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
 
@@ -45,10 +45,6 @@ module Ci
             query: "%#{query.try(:downcase)}%")
     end
 
-    def gl_projects_ids
-      projects.select(:gitlab_id)
-    end
-
     def set_default_values
       self.token = SecureRandom.hex(15) if self.token.blank?
     end
diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb
index 3f4fc43873eb2844793d4e6af0ebd246a4e64965..93d9be144e80dfeadfcade57f6cbb2b34f351e7d 100644
--- a/app/models/ci/runner_project.rb
+++ b/app/models/ci/runner_project.rb
@@ -14,8 +14,8 @@ module Ci
     extend Ci::Model
     
     belongs_to :runner, class_name: 'Ci::Runner'
-    belongs_to :project, class_name: 'Ci::Project'
+    belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
 
-    validates_uniqueness_of :runner_id, scope: :project_id
+    validates_uniqueness_of :runner_id, scope: :gl_project_id
   end
 end
diff --git a/app/models/ci/service.rb b/app/models/ci/service.rb
deleted file mode 100644
index 8063c51e82b27861e3e0e4909a0e13ee4fb97776..0000000000000000000000000000000000000000
--- a/app/models/ci/service.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_services
-#
-#  id         :integer          not null, primary key
-#  type       :string(255)
-#  title      :string(255)
-#  project_id :integer          not null
-#  created_at :datetime
-#  updated_at :datetime
-#  active     :boolean          default(FALSE), not null
-#  properties :text
-#
-
-# To add new service you should build a class inherited from Service
-# and implement a set of methods
-module Ci
-  class Service < ActiveRecord::Base
-    extend Ci::Model
-    
-    serialize :properties, JSON
-
-    default_value_for :active, false
-
-    after_initialize :initialize_properties
-
-    belongs_to :project, class_name: 'Ci::Project'
-
-    validates :project_id, presence: true
-
-    def activated?
-      active
-    end
-
-    def category
-      :common
-    end
-
-    def initialize_properties
-      self.properties = {} if properties.nil?
-    end
-
-    def title
-      # implement inside child
-    end
-
-    def description
-      # implement inside child
-    end
-
-    def help
-      # implement inside child
-    end
-
-    def to_param
-      # implement inside child
-    end
-
-    def fields
-      # implement inside child
-      []
-    end
-
-    def can_test?
-      project.builds.any?
-    end
-
-    def can_execute?(build)
-      true
-    end
-
-    def execute(build)
-      # implement inside child
-    end
-
-    # Provide convenient accessor methods
-    # for each serialized property.
-    def self.prop_accessor(*args)
-      args.each do |arg|
-        class_eval %{
-          def #{arg}
-            (properties || {})['#{arg}']
-          end
-
-          def #{arg}=(value)
-            self.properties ||= {}
-            self.properties['#{arg}'] = value
-          end
-        }
-      end
-    end
-
-    def self.boolean_accessor(*args)
-      self.prop_accessor(*args)
-
-      args.each do |arg|
-        class_eval %{
-          def #{arg}?
-            ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
-          end
-        }
-      end
-    end
-  end
-end
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index b73c35d5ae507156eb037aea782bc01535a48055..23516709a4122f82be4d0a2d8011b8d8b5c76dce 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -16,7 +16,7 @@ module Ci
 
     acts_as_paranoid
 
-    belongs_to :project, class_name: 'Ci::Project'
+    belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
     has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
 
     validates_presence_of :token
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index b3d2b809e03c659a03df3a359fe385f453303d9b..56759d3e50f3e41b0527404c212105c449502f4e 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -15,10 +15,10 @@ module Ci
   class Variable < ActiveRecord::Base
     extend Ci::Model
     
-    belongs_to :project, class_name: 'Ci::Project'
+    belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
 
     validates_presence_of :key
-    validates_uniqueness_of :key, scope: :project_id
+    validates_uniqueness_of :key, scope: :gl_project_id
 
     attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base
   end
diff --git a/app/models/ci/web_hook.rb b/app/models/ci/web_hook.rb
deleted file mode 100644
index 7ca16a1bde82fc7f2d603fe010d48ba6d844d4c5..0000000000000000000000000000000000000000
--- a/app/models/ci/web_hook.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_web_hooks
-#
-#  id         :integer          not null, primary key
-#  url        :string(255)      not null
-#  project_id :integer          not null
-#  created_at :datetime
-#  updated_at :datetime
-#
-
-module Ci
-  class WebHook < ActiveRecord::Base
-    extend Ci::Model
-
-    include HTTParty
-
-    belongs_to :project, class_name: 'Ci::Project'
-
-    # HTTParty timeout
-    default_timeout 10
-
-    validates :url, presence: true,
-                    format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
-
-    def execute(data)
-      parsed_url = URI.parse(url)
-      if parsed_url.userinfo.blank?
-        Ci::WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false)
-      else
-        post_url = url.gsub("#{parsed_url.userinfo}@", "")
-        auth = {
-          username: URI.decode(parsed_url.user),
-          password: URI.decode(parsed_url.password),
-        }
-        Ci::WebHook.post(post_url,
-                     body: data.to_json,
-                     headers: { "Content-Type" => "application/json" },
-                     verify: false,
-                     basic_auth: auth)
-      end
-    end
-  end
-end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 492f6be1ce39be2995c71c57ec2dfa0f2ce44c98..0ba7b584d91191983c3b2a6f4bdbf5ef71586f02 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -7,7 +7,7 @@ class Commit
   include Referable
   include StaticModel
 
-  attr_mentionable :safe_message
+  attr_mentionable :safe_message, pipeline: :single_line
   participant :author, :committer, :notes
 
   attr_accessor :project
@@ -78,11 +78,23 @@ class Commit
     }x
   end
 
+  def self.link_reference_pattern
+    super("commit", /(?<commit>\h{6,40})/)
+  end
+
   def to_reference(from_project = nil)
     if cross_project_reference?(from_project)
-      "#{project.to_reference}@#{id}"
+      project.to_reference + self.class.reference_prefix + self.id
     else
-      id
+      self.id
+    end
+  end
+
+  def reference_link_text(from_project = nil)
+    if cross_project_reference?(from_project)
+      project.to_reference + self.class.reference_prefix + self.short_id
+    else
+      self.short_id
     end
   end
 
@@ -135,10 +147,10 @@ class Commit
     description.present?
   end
 
-  def hook_attrs
+  def hook_attrs(with_changed_files: false)
     path_with_namespace = project.path_with_namespace
 
-    {
+    data = {
       id: id,
       message: safe_message,
       timestamp: committed_date.xmlschema,
@@ -148,6 +160,12 @@ class Commit
         email: author_email
       }
     }
+
+    if with_changed_files
+      data.merge!(repo_changes)
+    end
+
+    data
   end
 
   # Discover issues should be closed when this commit is pushed to a project's
@@ -157,11 +175,11 @@ class Commit
   end
 
   def author
-    @author ||= User.find_by_any_email(author_email)
+    @author ||= User.find_by_any_email(author_email.downcase)
   end
 
   def committer
-    @committer ||= User.find_by_any_email(committer_email)
+    @committer ||= User.find_by_any_email(committer_email.downcase)
   end
 
   def parents
@@ -196,4 +214,22 @@ class Commit
   def status
     ci_commit.try(:status) || :not_found
   end
+
+  private
+
+  def repo_changes
+    changes = { added: [], modified: [], removed: [] }
+
+    diffs.each do |diff|
+      if diff.deleted_file
+        changes[:removed] << diff.old_path
+      elsif diff.renamed_file || diff.new_file
+        changes[:added] << diff.new_path
+      else
+        changes[:modified] << diff.new_path
+      end
+    end
+
+    changes
+  end
 end
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 86fc9eb01a3e0077ce1a20a289c1a37474c109db..14e7971fa066175d79e5cdff056158ce5687698e 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -2,36 +2,38 @@
 #
 # Examples:
 #
-#   range = CommitRange.new('f3f85602...e86e1013')
+#   range = CommitRange.new('f3f85602...e86e1013', project)
 #   range.exclude_start?  # => false
 #   range.reference_title # => "Commits f3f85602 through e86e1013"
 #   range.to_s            # => "f3f85602...e86e1013"
 #
-#   range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae')
+#   range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
 #   range.exclude_start?  # => true
 #   range.reference_title # => "Commits f3f85602^ through e86e1013"
 #   range.to_param        # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
 #   range.to_s            # => "f3f85602..e86e1013"
 #
-#   # Assuming `project` is a Project with a repository containing both commits:
-#   range.project = project
+#   # Assuming the specified project has a repository containing both commits:
 #   range.valid_commits? # => true
 #
 class CommitRange
   include ActiveModel::Conversion
   include Referable
 
-  attr_reader :sha_from, :notation, :sha_to
+  attr_reader :commit_from, :notation, :commit_to
+  attr_reader :ref_from, :ref_to
 
   # Optional Project model
   attr_accessor :project
 
-  # See `exclude_start?`
-  attr_reader :exclude_start
-
-  # The beginning and ending SHAs can be between 6 and 40 hex characters, and
+  # The beginning and ending refs can be named or SHAs, and
   # the range notation can be double- or triple-dot.
-  PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
+  REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
+  PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
+
+  # In text references, the beginning and ending refs can only be SHAs
+  # between 6 and 40 hex characters.
+  STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
 
   def self.reference_prefix
     '@'
@@ -43,27 +45,40 @@ class CommitRange
   def self.reference_pattern
     %r{
       (?:#{Project.reference_pattern}#{reference_prefix})?
-      (?<commit_range>#{PATTERN})
+      (?<commit_range>#{STRICT_PATTERN})
     }x
   end
 
+  def self.link_reference_pattern
+    super("compare", /(?<commit_range>#{PATTERN})/)
+  end
+
   # Initialize a CommitRange
   #
   # range_string - The String commit range.
   # project      - An optional Project model.
   #
   # Raises ArgumentError if `range_string` does not match `PATTERN`.
-  def initialize(range_string, project = nil)
+  def initialize(range_string, project)
+    @project = project
+
     range_string.strip!
 
-    unless range_string.match(/\A#{PATTERN}\z/)
+    unless range_string =~ /\A#{PATTERN}\z/
       raise ArgumentError, "invalid CommitRange string format: #{range_string}"
     end
 
-    @exclude_start = !range_string.include?('...')
-    @sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2)
+    @ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2)
 
-    @project = project
+    if project.valid_repo?
+      @commit_from = project.commit(@ref_from)
+      @commit_to   = project.commit(@ref_to)
+    end
+
+    if valid_commits?
+      @ref_from = Commit.truncate_sha(sha_from) if sha_from.start_with?(@ref_from)
+      @ref_to   = Commit.truncate_sha(sha_to)   if sha_to.start_with?(@ref_to)
+    end
   end
 
   def inspect
@@ -71,15 +86,24 @@ class CommitRange
   end
 
   def to_s
-    "#{sha_from[0..7]}#{notation}#{sha_to[0..7]}"
+    sha_from + notation + sha_to
   end
 
+  alias_method :id, :to_s
+
   def to_reference(from_project = nil)
-    # Not using to_s because we want the full SHAs
-    reference = sha_from + notation + sha_to
+    if cross_project_reference?(from_project)
+      project.to_reference + self.class.reference_prefix + self.id
+    else
+      self.id
+    end
+  end
+
+  def reference_link_text(from_project = nil)
+    reference = ref_from + notation + ref_to
 
     if cross_project_reference?(from_project)
-      reference = project.to_reference + '@' + reference
+      reference = project.to_reference + self.class.reference_prefix + reference
     end
 
     reference
@@ -87,46 +111,58 @@ class CommitRange
 
   # Returns a String for use in a link's title attribute
   def reference_title
-    "Commits #{suffixed_sha_from} through #{sha_to}"
+    "Commits #{sha_start} through #{sha_to}"
   end
 
   # Return a Hash of parameters for passing to a URL helper
   #
   # See `namespace_project_compare_url`
   def to_param
-    { from: suffixed_sha_from, to: sha_to }
+    { from: sha_start, to: sha_to }
   end
 
   def exclude_start?
-    exclude_start
+    @notation == '..'
   end
 
   # Check if both the starting and ending commit IDs exist in a project's
   # repository
-  #
-  # project - An optional Project to check (default: `project`)
-  def valid_commits?(project = project)
-    return nil   unless project.present?
-    return false unless project.valid_repo?
-
-    commit_from.present? && commit_to.present?
+  def valid_commits?
+    commit_start.present? && commit_end.present?
   end
 
   def persisted?
     true
   end
 
-  def commit_from
-    @commit_from ||= project.repository.commit(suffixed_sha_from)
+  def sha_from
+    return nil unless @commit_from
+
+    @commit_from.id
+  end
+
+  def sha_to
+    return nil unless @commit_to
+
+    @commit_to.id
   end
 
-  def commit_to
-    @commit_to ||= project.repository.commit(sha_to)
+  def sha_start
+    return nil unless sha_from
+
+    exclude_start? ? sha_from + '^' : sha_from
   end
 
-  private
+  def commit_start
+    return nil unless sha_start
 
-  def suffixed_sha_from
-    sha_from + (exclude_start? ? '^' : '')
+    if exclude_start?
+      @commit_start ||= project.commit(sha_start)
+    else
+      commit_from
+    end
   end
+
+  alias_method :sha_end, :sha_to
+  alias_method :commit_end, :commit_to
 end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index e70f4d37184adea8d9664a2aa6efb8c17932c6b8..21c5c87bc3d28f6973b6e1a88ef7da379351f726 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,39 +1,36 @@
 # == Schema Information
 #
-# Table name: ci_builds
-#
-#  id                 :integer          not null, primary key
-#  project_id         :integer
-#  status             :string(255)
-#  finished_at        :datetime
-#  trace              :text
-#  created_at         :datetime
-#  updated_at         :datetime
-#  started_at         :datetime
-#  runner_id          :integer
-#  coverage           :float
-#  commit_id          :integer
-#  commands           :text
-#  job_id             :integer
-#  name               :string(255)
-#  deploy             :boolean          default(FALSE)
-#  options            :text
-#  allow_failure      :boolean          default(FALSE), not null
-#  stage              :string(255)
-#  trigger_request_id :integer
-#  stage_idx          :integer
-#  tag                :boolean
-#  ref                :string(255)
-#  user_id            :integer
-#  type               :string(255)
-#  target_url         :string(255)
-#  description        :string(255)
-#  artifacts_file     :text
+#  project_id            integer
+#  status                string
+#  finished_at           datetime
+#  trace                 text
+#  created_at            datetime
+#  updated_at            datetime
+#  started_at            datetime
+#  runner_id             integer
+#  coverage              float
+#  commit_id             integer
+#  commands              text
+#  job_id                integer
+#  name                  string
+#  deploy                boolean           default: false
+#  options               text
+#  allow_failure         boolean           default: false, null: false
+#  stage                 string
+#  trigger_request_id    integer
+#  stage_idx             integer
+#  tag                   boolean
+#  ref                   string
+#  user_id               integer
+#  type                  string
+#  target_url            string
+#  description           string
 #
 
 class CommitStatus < ActiveRecord::Base
   self.table_name = 'ci_builds'
 
+  belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
   belongs_to :commit, class_name: 'Ci::Commit'
   belongs_to :user
 
@@ -79,6 +76,10 @@ class CommitStatus < ActiveRecord::Base
       build.update_attributes finished_at: Time.now
     end
 
+    after_transition [:pending, :running] => :success do |build, transition|
+      MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.project, nil).trigger(build)
+    end
+
     state :pending, value: 'pending'
     state :running, value: 'running'
     state :failed, value: 'failed'
@@ -86,8 +87,7 @@ class CommitStatus < ActiveRecord::Base
     state :canceled, value: 'canceled'
   end
 
-  delegate :sha, :short_sha, :gl_project,
-           to: :commit, prefix: false
+  delegate :sha, :short_sha, to: :commit, prefix: false
 
   # TODO: this should be removed with all references
   def before_sha
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 68138688aab201cbe6b6eb1bd832e311d7875e4c..18a00f95b48291abe09e17d7f9cbe2869988cb19 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -8,6 +8,7 @@ module Issuable
   extend ActiveSupport::Concern
   include Participable
   include Mentionable
+  include StripAttribute
 
   included do
     belongs_to :author, class_name: "User"
@@ -49,8 +50,10 @@ module Issuable
              allow_nil: true,
              prefix: true
 
-    attr_mentionable :title, :description
+    attr_mentionable :title, pipeline: :single_line
+    attr_mentionable :description, cache: true
     participant :author, :assignee, :notes_with_associations
+    strip_attributes :title
   end
 
   module ClassMethods
@@ -92,14 +95,12 @@ module Issuable
     opened? || reopened?
   end
 
-  # Deprecated. Still exists to preserve API compatibility.
   def downvotes
-    0
+    notes.awards.where(note: "thumbsdown").count
   end
 
-  # Deprecated. Still exists to preserve API compatibility.
   def upvotes
-    0
+    notes.awards.where(note: "thumbsup").count
   end
 
   def subscribed?(user)
@@ -158,6 +159,14 @@ module Issuable
     self.class.to_s.underscore
   end
 
+  # Returns a Hash of attributes to be used for Twitter card metadata
+  def card_attributes
+    {
+      'Author'   => author.try(:name),
+      'Assignee' => assignee.try(:name)
+    }
+  end
+
   def notes_with_associations
     notes.includes(:author, :project)
   end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 193c91f1742940f4c87c1149ca2dec0d325a1c86..6316ee208b5a7c3b2ea60d9983f858858369feda 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -10,8 +10,9 @@ module Mentionable
 
   module ClassMethods
     # Indicate which attributes of the Mentionable to search for GFM references.
-    def attr_mentionable(*attrs)
-      mentionable_attrs.concat(attrs.map(&:to_s))
+    def attr_mentionable(attr, options = {})
+      attr = attr.to_s
+      mentionable_attrs << [attr, options]
     end
 
     # Accessor for attributes marked mentionable.
@@ -22,7 +23,7 @@ module Mentionable
 
   included do
     if self < Participable
-      participant ->(current_user) { mentioned_users(current_user, load_lazy_references: false) }
+      participant ->(current_user) { mentioned_users(current_user) }
     end
   end
 
@@ -37,38 +38,46 @@ module Mentionable
     "#{friendly_name} #{to_reference(from_project)}"
   end
 
-  # Construct a String that contains possible GFM references.
-  def mentionable_text
-    self.class.mentionable_attrs.map { |attr| send(attr) }.compact.join("\n\n")
-  end
-
   # The GFM reference to this Mentionable, which shouldn't be included in its #references.
   def local_reference
     self
   end
 
-  def all_references(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
-    ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references)
-    ext.analyze(text)
+  def all_references(current_user = self.author, text = nil)
+    ext = Gitlab::ReferenceExtractor.new(self.project, current_user, self.author)
+
+    if text
+      ext.analyze(text)
+    else
+      self.class.mentionable_attrs.each do |attr, options|
+        text = send(attr)
+        options[:cache_key] = [self, attr] if options.delete(:cache) && self.persisted?
+        ext.analyze(text, options)
+      end
+    end
+
     ext
   end
 
-  def mentioned_users(current_user = nil, load_lazy_references: true)
-    all_references(current_user, load_lazy_references: load_lazy_references).users
+  def mentioned_users(current_user = nil)
+    all_references(current_user).users
   end
 
   # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
-  def referenced_mentionables(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
-    return [] if text.blank?
+  def referenced_mentionables(current_user = self.author, text = nil)
+    refs = all_references(current_user, text)
+    refs = (refs.issues + refs.merge_requests + refs.commits)
 
-    refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
-    (refs.issues + refs.merge_requests + refs.commits) - [local_reference]
+    # We're using this method instead of Array diffing because that requires
+    # both of the object's `hash` values to be the same, which may not be the
+    # case for otherwise identical Commit objects.
+    refs.reject { |ref| ref == local_reference }
   end
 
-  # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
-  def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
+  # Create a cross-reference Note for each GFM reference to another Mentionable found in the +mentionable_attrs+.
+  def create_cross_references!(author = self.author, without = [], text = nil)
     refs = referenced_mentionables(author, text)
-    
+
     # We're using this method instead of Array diffing because that requires
     # both of the object's `hash` values to be the same, which may not be the
     # case for otherwise identical Commit objects.
@@ -106,12 +115,12 @@ module Mentionable
   def detect_mentionable_changes
     source = (changes.present? ? changes : previous_changes).dup
 
-    mentionable = self.class.mentionable_attrs
+    mentionable = self.class.mentionable_attrs.map { |attr, options| attr }
 
     # Only include changed fields that are mentionable
     source.select { |key, val| mentionable.include?(key) }
   end
-  
+
   # Determine whether or not a cross-reference Note has already been created between this Mentionable and
   # the specified target.
   def cross_reference_exists?(target)
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 85367f89f4f381f4f8d46e2266479eacefb86904..fc6f83b918b4929240bd85acfb9906af1ea51b1a 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -37,21 +37,22 @@ module Participable
 
   # Be aware that this method makes a lot of sql queries.
   # Save result into variable if you are going to reuse it inside same request
-  def participants(current_user = self.author, load_lazy_references: true)
-    participants = self.class.participant_attrs.flat_map do |attr|
-      value =
-        if attr.respond_to?(:call)
-          instance_exec(current_user, &attr)
-        else
-          send(attr)
-        end
+  def participants(current_user = self.author)
+    participants =
+      Gitlab::ReferenceExtractor.lazily do
+        self.class.participant_attrs.flat_map do |attr|
+          value =
+            if attr.respond_to?(:call)
+              instance_exec(current_user, &attr)
+            else
+              send(attr)
+            end
 
-      participants_for(value, current_user)
-    end.compact.uniq
-
-    if load_lazy_references
-      participants = Gitlab::Markdown::ReferenceFilter::LazyReference.load(participants).uniq
+          participants_for(value, current_user)
+        end.compact.uniq
+      end
 
+    unless Gitlab::ReferenceExtractor.lazy?
       participants.select! do |user|
         user.can?(:read_project, project)
       end
@@ -64,12 +65,12 @@ module Participable
 
   def participants_for(value, current_user = nil)
     case value
-    when User, Gitlab::Markdown::ReferenceFilter::LazyReference
+    when User, Banzai::LazyReference
       [value]
     when Enumerable, ActiveRecord::Relation
       value.flat_map { |v| participants_for(v, current_user) }
     when Participable
-      value.participants(current_user, load_lazy_references: false)
+      value.participants(current_user)
     end
   end
 end
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index cced66cc1e4916ef84ea41395ab12aad7a4bacfb..ce064f675ae0387afbbf4a978d8e9baa13e18eb0 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -21,6 +21,10 @@ module Referable
     ''
   end
 
+  def reference_link_text(from_project = nil)
+    to_reference(from_project)
+  end
+
   module ClassMethods
     # The character that prefixes the actual reference identifier
     #
@@ -44,6 +48,25 @@ module Referable
     def reference_pattern
       raise NotImplementedError, "#{self} does not implement #{__method__}"
     end
+
+    def link_reference_pattern(route, pattern)
+      %r{
+        (?<url>
+          #{Regexp.escape(Gitlab.config.gitlab.url)}
+          \/#{Project.reference_pattern}
+          \/#{Regexp.escape(route)}
+          \/#{pattern}
+          (?<path>
+            (\/[a-z0-9_=-]+)*
+          )?
+          (?<query>
+            \?[a-z0-9_=-]+
+            (&[a-z0-9_=-]+)*
+          )?
+          (?<anchor>\#[a-z0-9_-]+)?
+        )
+      }x
+    end
   end
 
   private
diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8806ebe897a246859dfbb8878a1671f982dc9593
--- /dev/null
+++ b/app/models/concerns/strip_attribute.rb
@@ -0,0 +1,34 @@
+# == Strip Attribute module
+#
+# Contains functionality to clean attributes before validation
+#
+# Usage:
+#
+#     class Milestone < ActiveRecord::Base
+#       strip_attributes :title
+#     end
+#
+#
+module StripAttribute
+  extend ActiveSupport::Concern
+
+  module ClassMethods
+    def strip_attributes(*attrs)
+      strip_attrs.concat(attrs)
+    end
+
+    def strip_attrs
+      @strip_attrs ||= []
+    end
+  end
+
+  included do
+    before_validation :strip_attributes
+  end
+
+  def strip_attributes
+    self.class.strip_attrs.each do |attr|
+      self[attr].strip! if self[attr] && self[attr].respond_to?(:strip!)
+    end
+  end
+end
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 9b88ec1cc3840f3e70afacdca7666ce19551783c..885deaf78d2d480b0ad416a04f0d3a970ed45833 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -1,31 +1,49 @@
 module TokenAuthenticatable
   extend ActiveSupport::Concern
 
-  module ClassMethods
-    def find_by_authentication_token(authentication_token = nil)
-      if authentication_token
-        where(authentication_token: authentication_token).first
-      end
+  class_methods do
+    def authentication_token_fields
+      @token_fields || []
     end
-  end
 
-  def ensure_authentication_token
-    if authentication_token.blank?
-      self.authentication_token = generate_authentication_token
-    end
-  end
+    private
 
-  def reset_authentication_token!
-    self.authentication_token = generate_authentication_token
-    save
+    def add_authentication_token_field(token_field)
+      @token_fields = [] unless @token_fields
+      @token_fields << token_field
+
+      define_singleton_method("find_by_#{token_field}") do |token|
+        find_by(token_field => token) if token
+      end
+
+      define_method("ensure_#{token_field}") do
+        current_token = read_attribute(token_field)
+        current_token.blank? ? write_new_token(token_field) : current_token
+      end
+
+      define_method("ensure_#{token_field}!") do
+        send("reset_#{token_field}!") if read_attribute(token_field).blank?
+        read_attribute(token_field)
+      end
+
+      define_method("reset_#{token_field}!") do
+        write_new_token(token_field)
+        save!
+      end
+    end
   end
 
   private
 
-  def generate_authentication_token
+  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.where(authentication_token: token).first
+      break token unless self.class.unscoped.find_by(token_field => token)
     end
   end
 end
diff --git a/app/models/event.rb b/app/models/event.rb
index 9afd223bce578c23df0b005474bdfe20ff956c7e..01d008035a585875f247bfa62274a7cffdc17d9a 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -201,7 +201,7 @@ class Event < ActiveRecord::Base
     elsif commented?
       "commented on"
     elsif created_project?
-      if project.import?
+      if project.external_import?
         "imported"
       else
         "created"
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 1321ccd963fb449491967fe1365c32394899e56a..af1d7562ebe2a5312fdcf1e477a1af0812952e4f 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -16,7 +16,15 @@ class GlobalMilestone
   end
 
   def safe_title
-    @title.parameterize
+    @title.to_slug.normalize.to_s
+  end
+
+  def expired?
+    if due_date
+      due_date.past?
+    else
+      false
+    end
   end
 
   def projects
@@ -98,4 +106,25 @@ class GlobalMilestone
   def complete?
     total_items_count == closed_items_count
   end
+
+  def due_date
+    return @due_date if defined?(@due_date)
+
+    @due_date =
+      if @milestones.all? { |x| x.due_date == @milestones.first.due_date }
+        @milestones.first.due_date
+      else
+        nil
+      end
+  end
+
+  def expires_at
+    if due_date
+      if due_date.past?
+        "expired at #{due_date.stamp("Aug 21, 2011")}"
+      else
+        "expires at #{due_date.stamp("Aug 21, 2011")}"
+      end
+    end
+  end
 end
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index 337b30971260d33555816c8f9ed0661799c849b6..22638057773ec926335d4738263af74a09e42d3d 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -25,4 +25,5 @@ class ProjectHook < WebHook
   scope :issue_hooks, -> { where(issues_events: true) }
   scope :note_hooks, -> { where(note_events: true) }
   scope :merge_request_hooks, -> { where(merge_requests_events: true) }
+  scope :build_hooks, -> { where(build_events: true) }
 end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index d6c6f415c4a1fd23e27686222589dbd70c046cb6..40eb0e20b4b4c7f6b4aa18064c12f8ba562e11d7 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -26,42 +26,44 @@ class WebHook < ActiveRecord::Base
   default_value_for :note_events, false
   default_value_for :merge_requests_events, false
   default_value_for :tag_push_events, false
+  default_value_for :build_events, false
   default_value_for :enable_ssl_verification, true
 
   # HTTParty timeout
   default_timeout Gitlab.config.gitlab.webhook_timeout
 
-  validates :url, presence: true,
-                  format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
+  validates :url, presence: true, url: true
 
   def execute(data, hook_name)
     parsed_url = URI.parse(url)
     if parsed_url.userinfo.blank?
-      WebHook.post(url,
-                   body: data.to_json,
-                   headers: {
-                     "Content-Type" => "application/json",
-                     "X-Gitlab-Event" => hook_name.singularize.titleize
-                   },
-                   verify: enable_ssl_verification)
+      response = WebHook.post(url,
+                              body: data.to_json,
+                              headers: {
+                                  "Content-Type" => "application/json",
+                                  "X-Gitlab-Event" => hook_name.singularize.titleize
+                              },
+                              verify: enable_ssl_verification)
     else
       post_url = url.gsub("#{parsed_url.userinfo}@", "")
       auth = {
         username: URI.decode(parsed_url.user),
         password: URI.decode(parsed_url.password),
       }
-      WebHook.post(post_url,
-                   body: data.to_json,
-                   headers: {
-                     "Content-Type" => "application/json",
-                     "X-Gitlab-Event" => hook_name.singularize.titleize
-                   },
-                   verify: enable_ssl_verification,
-                   basic_auth: auth)
+      response = WebHook.post(post_url,
+                              body: data.to_json,
+                              headers: {
+                                  "Content-Type" => "application/json",
+                                  "X-Gitlab-Event" => hook_name.singularize.titleize
+                              },
+                              verify: enable_ssl_verification,
+                              basic_auth: auth)
     end
-  rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
+
+    [response.code == 200, ActionView::Base.full_sanitizer.sanitize(response.to_s)]
+  rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
     logger.error("WebHook Error => #{e}")
-    false
+    [false, e.to_s]
   end
 
   def async_execute(data, hook_name)
diff --git a/app/models/identity.rb b/app/models/identity.rb
index ad60154be710f0af8092db93d76b28ad765b5f69..8bcdc1949538f9dc67a1de39223f20fc7d68f57f 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -12,6 +12,7 @@
 
 class Identity < ActiveRecord::Base
   include Sortable
+  include CaseSensitivity
   belongs_to :user
 
   validates :provider, presence: true
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 721831080334cefe53755d5df5d130af734a8931..80ecd15077f188864ad3719fa8bb223492cc5b58 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -69,6 +69,10 @@ class Issue < ActiveRecord::Base
     }x
   end
 
+  def self.link_reference_pattern
+    super("issues", /(?<issue>\d+)/)
+  end
+
   def to_reference(from_project = nil)
     reference = "#{self.class.reference_prefix}#{iid}"
 
@@ -79,6 +83,14 @@ class Issue < ActiveRecord::Base
     reference
   end
 
+  def referenced_merge_requests
+    Gitlab::ReferenceExtractor.lazily do
+      [self, *notes].flat_map do |note|
+        note.all_references.merge_requests
+      end
+    end.sort_by(&:iid)
+  end
+
   # Reset issue events cache
   #
   # Since we do cache @event we need to reset cache in special cases:
diff --git a/app/models/jira_issue.rb b/app/models/jira_issue.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5b21aac5e43c5902e67e0ed1af87aa155ae9fcce
--- /dev/null
+++ b/app/models/jira_issue.rb
@@ -0,0 +1,2 @@
+class JiraIssue < ExternalIssue
+end
diff --git a/app/models/label.rb b/app/models/label.rb
index b306aecbac1250d37d15b7f0eaa73c4e78393146..220da10a6abe86fabe7c9ec4b670f2e4a7166372 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -17,7 +17,7 @@ class Label < ActiveRecord::Base
   # Requests that have no label assigned.
   LabelStruct = Struct.new(:title, :name)
   None = LabelStruct.new('No Label', 'No Label')
-  Any = LabelStruct.new('Any', '')
+  Any = LabelStruct.new('Any Label', '')
 
   DEFAULT_COLOR = '#428BCA'
 
@@ -27,9 +27,7 @@ class Label < ActiveRecord::Base
   has_many :label_links, dependent: :destroy
   has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
 
-  validates :color,
-            format: { with: /\A#[0-9A-Fa-f]{6}\Z/ },
-            allow_blank: false
+  validates :color, color: true, allow_blank: false
   validates :project, presence: true, unless: Proc.new { |service| service.template? }
 
   # Don't allow '?', '&', and ',' for label titles
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 3c1426f59d08c085e17e92fdc0d8dc0a94af9e99..86b1b7e2f99839408e84502c5231d0649ef6aae3 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -1,3 +1,15 @@
+# == Schema Information
+#
+# Table name: lfs_objects
+#
+#  id         :integer          not null, primary key
+#  oid        :string(255)      not null
+#  size       :integer          not null
+#  created_at :datetime
+#  updated_at :datetime
+#  file       :string(255)
+#
+
 class LfsObject < ActiveRecord::Base
   has_many :lfs_objects_projects, dependent: :destroy
   has_many :projects, through: :lfs_objects_projects
@@ -5,4 +17,16 @@ class LfsObject < ActiveRecord::Base
   validates :oid, presence: true, uniqueness: true
 
   mount_uploader :file, LfsObjectUploader
+
+  def storage_project(project)
+    if project && project.forked?
+      storage_project(project.forked_from_project)
+    else
+      project
+    end
+  end
+
+  def project_allowed_access?(project)
+    projects.exists?(storage_project(project).id)
+  end
 end
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index 0fd5f089db9076d03d7c17fcea5518964d688809..890736bfc80bac8d369d23e4a920141afb1f6b8e 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -1,3 +1,14 @@
+# == Schema Information
+#
+# Table name: lfs_objects_projects
+#
+#  id            :integer          not null, primary key
+#  lfs_object_id :integer          not null
+#  project_id    :integer          not null
+#  created_at    :datetime
+#  updated_at    :datetime
+#
+
 class LfsObjectsProject < ActiveRecord::Base
   belongs_to :project
   belongs_to :lfs_object
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 2b336ca8927de0f554e2c2c03183ebbd039d13ae..685ade6efbe3075b65f61d5e3dec2bca4f16cba0 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -2,25 +2,28 @@
 #
 # Table name: merge_requests
 #
-#  id                :integer          not null, primary key
-#  target_branch     :string(255)      not null
-#  source_branch     :string(255)      not null
-#  source_project_id :integer          not null
-#  author_id         :integer
-#  assignee_id       :integer
-#  title             :string(255)
-#  created_at        :datetime
-#  updated_at        :datetime
-#  milestone_id      :integer
-#  state             :string(255)
-#  merge_status      :string(255)
-#  target_project_id :integer          not null
-#  iid               :integer
-#  description       :text
-#  position          :integer          default(0)
-#  locked_at         :datetime
-#  updated_by_id     :integer
-#  merge_error       :string(255)
+#  id                         :integer          not null, primary key
+#  target_branch              :string(255)      not null
+#  source_branch              :string(255)      not null
+#  source_project_id          :integer          not null
+#  author_id                  :integer
+#  assignee_id                :integer
+#  title                      :string(255)
+#  created_at                 :datetime
+#  updated_at                 :datetime
+#  milestone_id               :integer
+#  state                      :string(255)
+#  merge_status               :string(255)
+#  target_project_id          :integer          not null
+#  iid                        :integer
+#  description                :text
+#  position                   :integer          default(0)
+#  locked_at                  :datetime
+#  updated_by_id              :integer
+#  merge_error                :string(255)
+#  merge_params               :text (serialized to hash)
+#  merge_when_build_succeeds  :boolean          default(false), not null
+#  merge_user_id              :integer
 #
 
 require Rails.root.join("app/models/commit")
@@ -35,9 +38,12 @@ class MergeRequest < ActiveRecord::Base
 
   belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
   belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
+  belongs_to :merge_user, class_name: "User"
 
   has_one :merge_request_diff, dependent: :destroy
 
+  serialize :merge_params, Hash
+
   after_create :create_merge_request_diff
   after_update :update_merge_request_diff
 
@@ -121,6 +127,7 @@ class MergeRequest < ActiveRecord::Base
   validates :source_branch, presence: true
   validates :target_project, presence: true
   validates :target_branch, presence: true
+  validates :merge_user, presence: true, if: :merge_when_build_succeeds?
   validate :validate_branches
   validate :validate_fork
 
@@ -151,6 +158,10 @@ class MergeRequest < ActiveRecord::Base
     }x
   end
 
+  def self.link_reference_pattern
+    super("merge_requests", /(?<merge_request>\d+)/)
+  end
+
   def to_reference(from_project = nil)
     reference = "#{self.class.reference_prefix}#{iid}"
 
@@ -183,9 +194,7 @@ class MergeRequest < ActiveRecord::Base
       similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
       if similar_mrs.any?
         errors.add :validate_branches,
-                   "Cannot Create: This merge request already exists: #{
-                   similar_mrs.pluck(:title)
-                   }"
+                   "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}"
       end
     end
   end
@@ -254,6 +263,16 @@ class MergeRequest < ActiveRecord::Base
     end
   end
 
+  def can_cancel_merge_when_build_succeeds?(current_user)
+    can_be_merged_by?(current_user) || self.author == current_user
+  end
+
+  def can_remove_source_branch?(current_user)
+    !source_project.protected_branch?(source_branch) &&
+      !source_project.root_ref?(source_branch) &&
+      Ability.abilities.allowed?(current_user, :push_code, source_project)
+  end
+
   def mr_and_commit_notes
     # Fetch comments only from last 100 commits
     commits_for_notes_limit = 100
@@ -291,7 +310,7 @@ class MergeRequest < ActiveRecord::Base
       work_in_progress: work_in_progress?
     }
 
-    unless last_commit.nil?
+    if last_commit
       attrs.merge!(last_commit: last_commit.hook_attrs)
     end
 
@@ -316,7 +335,7 @@ class MergeRequest < ActiveRecord::Base
       issues = commits.flat_map { |c| c.closes_issues(current_user) }
       issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
                   closed_by_message(description))
-      issues.uniq.sort_by(&:id)
+      issues.uniq(&:id)
     else
       []
     end
@@ -389,6 +408,16 @@ class MergeRequest < ActiveRecord::Base
     message
   end
 
+  def reset_merge_when_build_succeeds
+    return unless merge_when_build_succeeds?
+
+    self.merge_when_build_succeeds = false
+    self.merge_user = nil
+    self.merge_params = nil
+
+    self.save
+  end
+
   # Return array of possible target branches
   # depends on target project of MR
   def target_branches
@@ -476,8 +505,10 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def ci_commit
-    if last_commit and source_project
-      source_project.ci_commit(last_commit.id)
-    end
+    @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
+  end
+
+  def broken?
+    self.commits.blank? || branch_missing? || cannot_be_merged?
   end
 end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 2ff16e2825c71ff7eb89eb905505048ebeedbd08..d8c7536cd316fbd907116c3e0d671802dd2990de 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -16,12 +16,13 @@
 class Milestone < ActiveRecord::Base
   # Represents a "No Milestone" state used for filtering Issues and Merge
   # Requests that have no milestone assigned.
-  MilestoneStruct = Struct.new(:title, :name)
-  None = MilestoneStruct.new('No Milestone', 'No Milestone')
-  Any = MilestoneStruct.new('Any', '')
+  MilestoneStruct = Struct.new(:title, :name, :id)
+  None = MilestoneStruct.new('No Milestone', 'No Milestone', 0)
+  Any = MilestoneStruct.new('Any Milestone', '', -1)
 
   include InternalId
   include Sortable
+  include StripAttribute
 
   belongs_to :project
   has_many :issues
@@ -35,6 +36,8 @@ class Milestone < ActiveRecord::Base
   validates :title, presence: true
   validates :project, presence: true
 
+  strip_attributes :title
+
   state_machine :state, initial: :active do
     event :close do
       transition active: :closed
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 20b92e68d6100176484870b310972546b7af954a..adafabbec07602a5ccbebcb0a31828ac7fd5790e 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -23,19 +23,17 @@ class Namespace < ActiveRecord::Base
 
   validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
   validates :name,
-    presence: true, uniqueness: true,
     length: { within: 0..255 },
-    format: { with: Gitlab::Regex.namespace_name_regex,
-              message: Gitlab::Regex.namespace_name_regex_message }
+    namespace_name: true,
+    presence: true,
+    uniqueness: true
 
   validates :description, length: { within: 0..255 }
   validates :path,
-    uniqueness: { case_sensitive: false },
-    presence: true,
     length: { within: 1..255 },
-    exclusion: { in: Gitlab::Blacklist.path },
-    format: { with: Gitlab::Regex.namespace_regex,
-              message: Gitlab::Regex.namespace_regex_message }
+    namespace: true,
+    presence: true,
+    uniqueness: { case_sensitive: false }
 
   delegate :name, to: :owner, allow_nil: true, prefix: true
 
@@ -47,7 +45,7 @@ class Namespace < ActiveRecord::Base
 
   class << self
     def by_path(path)
-      where('lower(path) = :value', value: path.downcase).first
+      find_by('lower(path) = :value', value: path.downcase)
     end
 
     # Case insensetive search for namespace by path or name
@@ -150,6 +148,6 @@ class Namespace < ActiveRecord::Base
   end
 
   def find_fork_of(project)
-    projects.joins(:forked_project_link).where('forked_project_links.forked_from_project_id = ?', project.id).first
+    projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id)
   end
 end
diff --git a/app/models/note.rb b/app/models/note.rb
index 1c6345e735c4b94154aecce51de775996354ce77..3d5b663c99f0ff545c3682ac422cdcedf9b22e5a 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -16,6 +16,7 @@
 #  system        :boolean          default(FALSE), not null
 #  st_diff       :text
 #  updated_by_id :integer
+#  is_award      :boolean          default(FALSE), not null
 #
 
 require 'carrierwave/orm/activerecord'
@@ -28,7 +29,7 @@ class Note < ActiveRecord::Base
 
   default_value_for :system, false
 
-  attr_mentionable :note
+  attr_mentionable :note, cache: true, pipeline: :note
   participant :author
 
   belongs_to :project
@@ -39,9 +40,12 @@ class Note < ActiveRecord::Base
   delegate :name, to: :project, prefix: true
   delegate :name, :email, to: :author, prefix: true
 
+  before_validation :set_award!
+
   validates :note, :project, presence: true
   validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
-  validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
+  validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
+  validates :line_code, line_code: true, allow_blank: true
   # Attachments are deprecated and are handled by Markdown uploader
   validates :attachment, file_size: { maximum: :max_attachment_size }
 
@@ -103,9 +107,16 @@ class Note < ActiveRecord::Base
     end
 
     def grouped_awards
+      notes = {}
+
       awards.select(:note).distinct.map do |note|
-        [ note.note, where(note: note.note) ]
+        notes[note.note] = where(note: note.note)
       end
+
+      notes["thumbsup"] ||= Note.none
+      notes["thumbsdown"] ||= Note.none
+
+      notes
     end
   end
 
@@ -335,17 +346,43 @@ class Note < ActiveRecord::Base
     read_attribute(:system)
   end
 
-  # Deprecated. Still exists to preserve API compatibility.
   def downvote?
-    false
+    is_award && note == "thumbsdown"
   end
 
-  # Deprecated. Still exists to preserve API compatibility.
   def upvote?
-    false
+    is_award && note == "thumbsup"
   end
 
   def editable?
-    !system?
+    !system? && !is_award
+  end
+
+  # Checks if note is an award added as a comment
+  #
+  # If note is an award, this method sets is_award to true
+  #   and changes content of the note to award name.
+  #
+  # Method is executed as a before_validation callback.
+  #
+  def set_award!
+    return unless awards_supported? && contains_emoji_only?
+    self.is_award = true
+    self.note = award_emoji_name
+  end
+
+  private
+
+  def awards_supported?
+    noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)
+  end
+
+  def contains_emoji_only?
+    note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/
+  end
+
+  def award_emoji_name
+    original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
+    AwardEmoji.normilize_emoji_name(original_name)
   end
 end
diff --git a/app/models/project.rb b/app/models/project.rb
index f0a4b6aae7b2af1d52c6a964d0569f937e52d4ec..75f85310d5f24c465a3ba28d0cf46d3226cb8deb 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -28,6 +28,7 @@
 #  import_type            :string(255)
 #  import_source          :string(255)
 #  commit_count           :integer          default(0)
+#  import_error           :text
 #
 
 require 'carrierwave/orm/activerecord'
@@ -42,9 +43,8 @@ class Project < ActiveRecord::Base
   include Sortable
   include AfterCommitQueue
   include CaseSensitivity
-  
+
   extend Gitlab::ConfigHelper
-  extend Enumerize
 
   UNKNOWN_IMPORT_URL = 'http://unknown.git'
 
@@ -56,6 +56,7 @@ class Project < ActiveRecord::Base
   default_value_for :wiki_enabled, gitlab_config_features.wiki
   default_value_for :wall_enabled, false
   default_value_for :snippets_enabled, gitlab_config_features.snippets
+  default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
 
   # set last_activity_at to the same as created_at
   after_create :set_last_activity_at
@@ -63,6 +64,19 @@ class Project < ActiveRecord::Base
     update_column(:last_activity_at, self.created_at)
   end
 
+  # update visibility_levet of forks
+  after_update :update_forks_visibility_level
+  def update_forks_visibility_level
+    return unless visibility_level < visibility_level_was
+
+    forks.each do |forked_project|
+      if forked_project.visibility_level > visibility_level
+        forked_project.visibility_level = visibility_level
+        forked_project.save!
+      end
+    end
+  end
+
   ActsAsTaggableOn.strict_case_match = true
   acts_as_taggable_on :tags
 
@@ -77,10 +91,10 @@ class Project < ActiveRecord::Base
 
   # Project services
   has_many :services
-  has_one :gitlab_ci_service, dependent: :destroy
   has_one :campfire_service, dependent: :destroy
   has_one :drone_ci_service, dependent: :destroy
   has_one :emails_on_push_service, dependent: :destroy
+  has_one :builds_email_service, dependent: :destroy
   has_one :irker_service, dependent: :destroy
   has_one :pivotaltracker_service, dependent: :destroy
   has_one :hipchat_service, dependent: :destroy
@@ -99,9 +113,12 @@ class Project < ActiveRecord::Base
   has_one :gitlab_issue_tracker_service, dependent: :destroy
   has_one :external_wiki_service, dependent: :destroy
 
-  has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
+  has_one  :forked_project_link,  dependent: :destroy, foreign_key: "forked_to_project_id"
+  has_one  :forked_from_project,  through:   :forked_project_link
+
+  has_many :forked_project_links, foreign_key: "forked_from_project_id"
+  has_many :forks,                through:     :forked_project_links, source: :forked_to_project
 
-  has_one :forked_from_project, through: :forked_project_link
   # Merge Requests for target project should be removed with it
   has_many :merge_requests,     dependent: :destroy, foreign_key: 'target_project_id'
   # Merge requests from source project should be kept when source project was removed
@@ -121,14 +138,21 @@ class Project < ActiveRecord::Base
   has_many :deploy_keys, through: :deploy_keys_projects
   has_many :users_star_projects, dependent: :destroy
   has_many :starrers, through: :users_star_projects, source: :user
-  has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
-  has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build'
   has_many :releases, dependent: :destroy
   has_many :lfs_objects_projects, dependent: :destroy
   has_many :lfs_objects, through: :lfs_objects_projects
 
   has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
-  has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id
+
+  has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id
+  has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
+  has_many :builds, class_name: 'Ci::Build', foreign_key: :gl_project_id # the builds are created from the commit_statuses
+  has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject', foreign_key: :gl_project_id
+  has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
+  has_many :variables, dependent: :destroy, class_name: 'Ci::Variable', foreign_key: :gl_project_id
+  has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :gl_project_id
+
+  accepts_nested_attributes_for :variables, allow_destroy: true
 
   delegate :name, to: :owner, allow_nil: true, prefix: true
   delegate :members, to: :team, prefix: true
@@ -153,7 +177,7 @@ class Project < ActiveRecord::Base
   validates_uniqueness_of :name, scope: :namespace_id
   validates_uniqueness_of :path, scope: :namespace_id
   validates :import_url,
-    format: { with: /\A#{URI.regexp(%w(ssh git http https))}\z/, message: 'should be a valid url' },
+    url: { protocols: %w(ssh git http https) },
     if: :external_import?
   validates :star_count, numericality: { greater_than_or_equal_to: 0 }
   validate :check_limit, on: :create
@@ -161,6 +185,11 @@ class Project < ActiveRecord::Base
     if: ->(project) { project.avatar.present? && project.avatar_changed? }
   validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
 
+  before_validation :set_runners_token_token
+  def set_runners_token_token
+    self.runners_token = SecureRandom.hex(15) if self.runners_token.blank?
+  end
+
   mount_uploader :avatar, AvatarUploader
 
   # Scopes
@@ -252,10 +281,14 @@ class Project < ActiveRecord::Base
         joins(:namespace).
         iwhere('namespaces.path' => namespace_path)
 
-      projects.where('projects.path' => project_path).take ||
+      projects.find_by('projects.path' => project_path) ||
         projects.iwhere('projects.path' => project_path).take
     end
 
+    def find_by_ci_id(id)
+      find_by(ci_id: id.to_i)
+    end
+
     def visibility_levels
       Gitlab::VisibilityLevel.options
     end
@@ -433,7 +466,7 @@ class Project < ActiveRecord::Base
   end
 
   def external_issue_tracker
-    @external_issues_tracker ||= external_issues_trackers.select(&:activated?).first
+    @external_issues_tracker ||= external_issues_trackers.find(&:activated?)
   end
 
   def can_have_issues_tracker_id?
@@ -479,7 +512,11 @@ class Project < ActiveRecord::Base
   end
 
   def ci_service
-    @ci_service ||= ci_services.select(&:activated?).first
+    @ci_service ||= ci_services.find(&:activated?)
+  end
+
+  def jira_tracker?
+    issues_tracker.to_param == 'jira'
   end
 
   def avatar_type
@@ -530,7 +567,7 @@ class Project < ActiveRecord::Base
   end
 
   def project_member_by_name_or_email(name = nil, email = nil)
-    user = users.where('name like ? or email like ?', name, email).first
+    user = users.find_by('name like ? or email like ?', name, email)
     project_members.where(user: user) if user
   end
 
@@ -662,6 +699,7 @@ class Project < ActiveRecord::Base
         gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
         send_move_instructions(old_path_with_namespace)
         reset_events_cache
+        @repository = nil
       rescue
         # Returning false does not rollback after_* transaction but gives
         # us information about failing some of tasks
@@ -704,7 +742,7 @@ class Project < ActiveRecord::Base
   end
 
   def project_member(user)
-    project_members.where(user_id: user).first
+    project_members.find_by(user_id: user)
   end
 
   def default_branch
@@ -746,7 +784,7 @@ class Project < ActiveRecord::Base
   end
 
   def forks_count
-    ForkedProjectLink.where(forked_from_project_id: self.id).count
+    forks.count
   end
 
   def find_label(name)
@@ -781,6 +819,10 @@ class Project < ActiveRecord::Base
     false
   end
 
+  def jira_tracker_active?
+    jira_tracker? && jira_service.active
+  end
+
   def ci_commit(sha)
     ci_commits.find_by(sha: sha)
   end
@@ -789,28 +831,6 @@ class Project < ActiveRecord::Base
     ci_commit(sha) || ci_commits.create(sha: sha)
   end
 
-  def ensure_gitlab_ci_project
-    gitlab_ci_project || create_gitlab_ci_project(
-      shared_runners_enabled: current_application_settings.shared_runners_enabled
-    )
-  end
-
-  # TODO: this should be migrated to Project table,
-  # the same as issues_enabled
-  def builds_enabled
-    gitlab_ci_service && gitlab_ci_service.active
-  end
-
-  def builds_enabled?
-    builds_enabled
-  end
-
-  def builds_enabled=(value)
-    service = gitlab_ci_service || create_gitlab_ci_service
-    service.active = value
-    service.save
-  end
-
   def enable_ci
     self.builds_enabled = true
   end
@@ -824,4 +844,43 @@ class Project < ActiveRecord::Base
       forked_project_link.destroy
     end
   end
+
+  def any_runners?(&block)
+    if runners.active.any?(&block)
+      return true
+    end
+
+    shared_runners_enabled? && Ci::Runner.shared.active.any?(&block)
+  end
+
+  def valid_runners_token? token
+    self.runners_token && self.runners_token == token
+  end
+
+  # TODO (ayufan): For now we use runners_token (backward compatibility)
+  # In 8.4 every build will have its own individual token valid for time of build
+  def valid_build_token? token
+    self.builds_enabled? && self.runners_token && self.runners_token == token
+  end
+
+  def build_coverage_enabled?
+    build_coverage_regex.present?
+  end
+
+  def build_timeout_in_minutes
+    build_timeout / 60
+  end
+
+  def build_timeout_in_minutes=(value)
+    self.build_timeout = value.to_i * 60
+  end
+
+  def open_issues_count
+    issues.opened.count
+  end
+
+  def visibility_level_allowed?(level)
+    return true unless forked?
+    Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level.to_i)
+  end
 end
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index d31b12f539e9bb9dee0e3903761d418f7d2004e4..aa8746beb806acef7310ce140ee734bb5ce9801d 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -23,19 +23,14 @@ class BambooService < CiService
 
   prop_accessor :bamboo_url, :build_key, :username, :password
 
-  validates :bamboo_url,
-    presence: true,
-    format: { with: /\A#{URI.regexp}\z/ },
-    if: :activated?
+  validates :bamboo_url, presence: true, url: true, if: :activated?
   validates :build_key, presence: true, if: :activated?
   validates :username,
     presence: true,
-    if: ->(service) { service.password? },
-    if: :activated?
+    if: ->(service) { service.activated? && service.password }
   validates :password,
     presence: true,
-    if: ->(service) { service.username? },
-    if: :activated?
+    if: ->(service) { service.activated? && service.username }
 
   attr_accessor :response
 
@@ -84,7 +79,7 @@ class BambooService < CiService
   def supported_events
     %w(push)
   end
-  
+
   def build_info(sha)
     url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
 
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 40058b53df556742190f3566bb61cb6ebdf5d8f8..199ee3a9d0df2b53cc3ba5bd3fe9f548c54be5e8 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -37,7 +37,7 @@ class BuildkiteService < CiService
   def compose_service_hook
     hook = service_hook || build_service_hook
     hook.url = webhook_url
-    hook.enable_ssl_verification = enable_ssl_verification
+    hook.enable_ssl_verification = !!enable_ssl_verification
     hook.save
   end
 
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8247c79fc336c0e8074e2ad3fb4b25f76f57f979
--- /dev/null
+++ b/app/models/project_services/builds_email_service.rb
@@ -0,0 +1,90 @@
+# == Schema Information
+#
+# Table name: services
+#
+#  id                    :integer          not null, primary key
+#  type                  :string(255)
+#  title                 :string(255)
+#  project_id            :integer
+#  created_at            :datetime
+#  updated_at            :datetime
+#  active                :boolean          default(FALSE), not null
+#  properties            :text
+#  template              :boolean          default(FALSE)
+#  push_events           :boolean          default(TRUE)
+#  issues_events         :boolean          default(TRUE)
+#  merge_requests_events :boolean          default(TRUE)
+#  tag_push_events       :boolean          default(TRUE)
+#  note_events           :boolean          default(TRUE), not null
+#
+
+class BuildsEmailService < Service
+  prop_accessor :recipients
+  boolean_accessor :add_pusher
+  boolean_accessor :notify_only_broken_builds
+  validates :recipients, presence: true, if: :activated?
+
+  def initialize_properties
+    if properties.nil?
+      self.properties = {}
+      self.notify_only_broken_builds = true
+    end
+  end
+
+  def title
+    'Builds emails'
+  end
+
+  def description
+    'Email the builds status to a list of recipients.'
+  end
+
+  def to_param
+    'builds_email'
+  end
+
+  def supported_events
+    %w(build)
+  end
+
+  def execute(push_data)
+    return unless supported_events.include?(push_data[:object_kind])
+
+    if should_build_be_notified?(push_data)
+      BuildEmailWorker.perform_async(
+        push_data[:build_id],
+        all_recipients(push_data),
+        push_data,
+      )
+    end
+  end
+
+  def fields
+    [
+      { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by comma' },
+      { type: 'checkbox', name: 'add_pusher', label: 'Add pusher to recipients list' },
+      { type: 'checkbox', name: 'notify_only_broken_builds' },
+    ]
+  end
+
+  def should_build_be_notified?(data)
+    case data[:build_status]
+    when 'success'
+      !notify_only_broken_builds?
+    when 'failed'
+      true
+    else
+      false
+    end
+  end
+
+  def all_recipients(data)
+    all_recipients = recipients.split(',')
+
+    if add_pusher? && data[:user][:email]
+      all_recipients << "#{data[:user][:email]}"
+    end
+
+    all_recipients
+  end
+end
diff --git a/app/models/project_services/ci/hip_chat_message.rb b/app/models/project_services/ci/hip_chat_message.rb
deleted file mode 100644
index d89466b689f8aeda158c3ea655bf8d5b4f8a6a33..0000000000000000000000000000000000000000
--- a/app/models/project_services/ci/hip_chat_message.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-module Ci
-  class HipChatMessage
-    include Gitlab::Application.routes.url_helpers
-
-    attr_reader :build
-
-    def initialize(build)
-      @build = build
-    end
-
-    def to_s
-      lines = Array.new
-      lines.push("<a href=\"#{ci_project_url(project)}\">#{project.name}</a> - ")
-      lines.push("<a href=\"#{builds_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}\">Commit ##{commit.id}</a></br>")
-      lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>")
-      lines.push("#{humanized_status(commit_status)} in #{commit.duration} second(s).")
-      lines.join('')
-    end
-
-    def status_color(build_or_commit=nil)
-      build_or_commit ||= commit_status
-      case build_or_commit
-      when :success
-        'green'
-      when :failed, :canceled
-        'red'
-      else # :pending, :running or unknown
-        'yellow'
-      end
-    end
-
-    def notify?
-      [:failed, :canceled].include?(commit_status)
-    end
-
-
-    private
-
-    def commit
-      build.commit
-    end
-
-    def project
-      commit.project
-    end
-
-    def build_status
-      build.status.to_sym
-    end
-
-    def commit_status
-      commit.status.to_sym
-    end
-
-    def humanized_status(build_or_commit=nil)
-      build_or_commit ||= commit_status
-      case build_or_commit
-      when :pending
-        "Pending"
-      when :running
-        "Running"
-      when :failed
-        "Failed"
-      when :success
-        "Successful"
-      when :canceled
-        "Canceled"
-      else
-        "Unknown"
-      end
-    end
-  end
-end
diff --git a/app/models/project_services/ci/hip_chat_service.rb b/app/models/project_services/ci/hip_chat_service.rb
deleted file mode 100644
index 0df03890efb2f838a2f54ba3430ff64ba4f6cdc2..0000000000000000000000000000000000000000
--- a/app/models/project_services/ci/hip_chat_service.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_services
-#
-#  id         :integer          not null, primary key
-#  type       :string(255)
-#  title      :string(255)
-#  project_id :integer          not null
-#  created_at :datetime
-#  updated_at :datetime
-#  active     :boolean          default(FALSE), not null
-#  properties :text
-#
-
-module Ci
-  class HipChatService < Ci::Service
-    prop_accessor :hipchat_token, :hipchat_room, :hipchat_server
-    boolean_accessor :notify_only_broken_builds
-    validates :hipchat_token, presence: true, if: :activated?
-    validates :hipchat_room, presence: true, if: :activated?
-    default_value_for :notify_only_broken_builds, true
-
-    def title
-      "HipChat"
-    end
-
-    def description
-      "Private group chat, video chat, instant messaging for teams"
-    end
-
-    def help
-    end
-
-    def to_param
-      'hip_chat'
-    end
-
-    def fields
-      [
-        { type: 'text', name: 'hipchat_token',  label: 'Token', placeholder: '' },
-        { type: 'text', name: 'hipchat_room',   label: 'Room', placeholder: '' },
-        { type: 'text', name: 'hipchat_server', label: 'Server', placeholder: 'https://hipchat.example.com', help: 'Leave blank for default' },
-        { type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' }
-      ]
-    end
-
-    def can_execute?(build)
-      return if build.allow_failure?
-
-      commit = build.commit
-      return unless commit
-      return unless commit.latest_builds.include? build
-
-      case commit.status.to_sym
-      when :failed
-        true
-      when :success
-        true unless notify_only_broken_builds?
-      else
-        false
-      end
-    end
-
-    def execute(build)
-      msg = Ci::HipChatMessage.new(build)
-      opts = default_options.merge(
-        token: hipchat_token,
-        room: hipchat_room,
-        server: server_url,
-        color: msg.status_color,
-        notify: msg.notify?
-      )
-      Ci::HipChatNotifierWorker.perform_async(msg.to_s, opts)
-    end
-
-    private
-
-    def default_options
-      {
-        service_name: 'GitLab CI',
-        message_format: 'html'
-      }
-    end
-
-    def server_url
-      if hipchat_server.blank?
-        'https://api.hipchat.com'
-      else
-        hipchat_server
-      end
-    end
-  end
-end
diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb
deleted file mode 100644
index d31dd6899c1eeb8939ec45eb0d1701872a7d343b..0000000000000000000000000000000000000000
--- a/app/models/project_services/ci/mail_service.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_services
-#
-#  id         :integer          not null, primary key
-#  type       :string(255)
-#  title      :string(255)
-#  project_id :integer          not null
-#  created_at :datetime
-#  updated_at :datetime
-#  active     :boolean          default(FALSE), not null
-#  properties :text
-#
-
-module Ci
-  class MailService < Ci::Service
-    delegate :email_recipients, :email_recipients=,
-             :email_add_pusher, :email_add_pusher=,
-             :email_only_broken_builds, :email_only_broken_builds=, to: :project, prefix: false
-
-    before_save :update_project
-
-    default_value_for :active, true
-
-    def title
-      'Mail'
-    end
-
-    def description
-      'Email notification'
-    end
-
-    def to_param
-      'mail'
-    end
-
-    def fields
-      [
-        { type: 'text', name: 'email_recipients', label: 'Recipients', help: 'Whitespace-separated list of recipient addresses' },
-        { type: 'checkbox', name: 'email_add_pusher', label: 'Add pusher to recipients list' },
-        { type: 'checkbox', name: 'email_only_broken_builds', label: 'Notify only broken builds' }
-      ]
-    end
-
-    def can_execute?(build)
-      return if build.allow_failure?
-
-      # it doesn't make sense to send emails for retried builds
-      commit = build.commit
-      return unless commit
-      return unless commit.latest_builds.include?(build)
-
-      case build.status.to_sym
-      when :failed
-        true
-      when :success
-        true unless email_only_broken_builds
-      else
-        false
-      end
-    end
-
-    def execute(build)
-      build.project_recipients.each do |recipient|
-        case build.status.to_sym
-        when :success
-          mailer.build_success_email(build.id, recipient)
-        when :failed
-          mailer.build_fail_email(build.id, recipient)
-        end
-      end
-    end
-
-    private
-
-    def update_project
-      project.save!
-    end
-
-    def mailer
-      Ci::Notify.delay
-    end
-  end
-end
diff --git a/app/models/project_services/ci/slack_message.rb b/app/models/project_services/ci/slack_message.rb
deleted file mode 100644
index 1a6ff8e34c98af15048640b6b9a6bab7fcad0f2b..0000000000000000000000000000000000000000
--- a/app/models/project_services/ci/slack_message.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-require 'slack-notifier'
-
-module Ci
-  class SlackMessage
-    include Gitlab::Application.routes.url_helpers
-
-    def initialize(commit)
-      @commit = commit
-    end
-
-    def pretext
-      ''
-    end
-
-    def color
-      attachment_color
-    end
-
-    def fallback
-      format(attachment_message)
-    end
-
-    def attachments
-      fields = []
-
-      commit.latest_builds.each do |build|
-        next if build.allow_failure?
-        next unless build.failed?
-        fields << {
-          title: build.name,
-          value: "Build <#{namespace_project_build_url(build.gl_project.namespace, build.gl_project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)."
-        }
-      end
-
-      [{
-         text: attachment_message,
-         color: attachment_color,
-         fields: fields
-       }]
-    end
-
-    private
-
-    attr_reader :commit
-
-    def attachment_message
-      out = "<#{ci_project_url(project)}|#{project_name}>: "
-      out << "Commit <#{builds_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}|\##{commit.id}> "
-      out << "(<#{commit_sha_link}|#{commit.short_sha}>) "
-      out << "of <#{commit_ref_link}|#{commit.ref}> "
-      out << "by #{commit.git_author_name} " if commit.git_author_name
-      out << "#{commit_status} in "
-      out << "#{commit.duration} second(s)"
-    end
-
-    def format(string)
-      Slack::Notifier::LinkFormatter.format(string)
-    end
-
-    def project
-      commit.project
-    end
-
-    def project_name
-      project.name
-    end
-
-    def commit_sha_link
-      "#{project.gitlab_url}/commit/#{commit.sha}"
-    end
-
-    def commit_ref_link
-      "#{project.gitlab_url}/commits/#{commit.ref}"
-    end
-
-    def attachment_color
-      if commit.success?
-        'good'
-      else
-        'danger'
-      end
-    end
-
-    def commit_status
-      if commit.success?
-        'succeeded'
-      else
-        'failed'
-      end
-    end
-  end
-end
diff --git a/app/models/project_services/ci/slack_service.rb b/app/models/project_services/ci/slack_service.rb
deleted file mode 100644
index 7064bfe78db59a2615639b27315da6bf3f7d567b..0000000000000000000000000000000000000000
--- a/app/models/project_services/ci/slack_service.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_services
-#
-#  id         :integer          not null, primary key
-#  type       :string(255)
-#  title      :string(255)
-#  project_id :integer          not null
-#  created_at :datetime
-#  updated_at :datetime
-#  active     :boolean          default(FALSE), not null
-#  properties :text
-#
-
-module Ci
-  class SlackService < Ci::Service
-    prop_accessor :webhook
-    boolean_accessor :notify_only_broken_builds
-    validates :webhook, presence: true, if: :activated?
-
-    default_value_for :notify_only_broken_builds, true
-
-    def title
-      'Slack'
-    end
-
-    def description
-      'A team communication tool for the 21st century'
-    end
-
-    def to_param
-      'slack'
-    end
-
-    def help
-      'Visit https://www.slack.com/services/new/incoming-webhook. Then copy link and save project!' unless webhook.present?
-    end
-
-    def fields
-      [
-        { type: 'text', name: 'webhook', label: 'Webhook URL', placeholder: '' },
-        { type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' }
-      ]
-    end
-
-    def can_execute?(build)
-      return if build.allow_failure?
-
-      commit = build.commit
-      return unless commit
-      return unless commit.latest_builds.include?(build)
-
-      case commit.status.to_sym
-      when :failed
-        true
-      when :success
-        true unless notify_only_broken_builds?
-      else
-        false
-      end
-    end
-
-    def execute(build)
-      message = Ci::SlackMessage.new(build.commit)
-      options = default_options.merge(
-        color: message.color,
-        fallback: message.fallback,
-        attachments: message.attachments
-      )
-      Ci::SlackNotifierWorker.perform_async(webhook, message.pretext, options)
-    end
-
-    private
-
-    def default_options
-      {
-        username: 'GitLab CI'
-      }
-    end
-  end
-end
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index 127684bd27457fbd1f32850a27900fcc64010269..08e5ccb38555f2b6381c1e0244c1f441d1a66e87 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -19,14 +19,11 @@
 #
 
 class DroneCiService < CiService
-  
+
   prop_accessor :drone_url, :token, :enable_ssl_verification
-  validates :drone_url,
-    presence: true,
-    format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
-  validates :token,
-    presence: true,
-    if: :activated?
+
+  validates :drone_url, presence: true, url: true, if: :activated?
+  validates :token, presence: true, if: :activated?
 
   after_save :compose_service_hook, if: :activated?
 
@@ -34,7 +31,7 @@ class DroneCiService < CiService
     hook = service_hook || build_service_hook
     # If using a service template, project may not be available
     hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join if project
-    hook.enable_ssl_verification = enable_ssl_verification
+    hook.enable_ssl_verification = !!enable_ssl_verification
     hook.save
   end
 
@@ -58,16 +55,16 @@ class DroneCiService < CiService
   end
 
   def merge_request_status_path(iid, sha = nil, ref = nil)
-    url = [drone_url, 
-           "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}", 
+    url = [drone_url,
+           "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
            "?access_token=#{token}"]
 
     URI.join(*url).to_s
   end
 
   def commit_status_path(sha, ref)
-    url = [drone_url, 
-           "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}", 
+    url = [drone_url,
+           "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
            "?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"]
 
     URI.join(*url).to_s
@@ -114,15 +111,15 @@ class DroneCiService < CiService
   end
 
   def merge_request_page(iid, sha, ref)
-    url = [drone_url, 
+    url = [drone_url,
            "gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"]
 
     URI.join(*url).to_s
   end
 
   def commit_page(sha, ref)
-    url = [drone_url, 
-           "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}", 
+    url = [drone_url,
+           "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
            "?branch=#{URI::encode(ref.to_s)}"]
 
     URI.join(*url).to_s
@@ -163,10 +160,10 @@ class DroneCiService < CiService
   end
 
   def push_valid?(data)
-    opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id, 
+    opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
                                                                 source_branch: Gitlab::Git.ref_name(data[:ref]))
 
-    opened_merge_requests.empty? && data[:total_commits_count] > 0 && 
+    opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
       !Gitlab::Git.blank_ref?(data[:after])
   end
 
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index 9c46af7e7213f08f30e47c1668ae74e99cff6824..74c57949b4d8f1e2e67aaa489a68111beaf4371d 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -22,10 +22,8 @@ class ExternalWikiService < Service
   include HTTParty
 
   prop_accessor :external_wiki_url
-  validates :external_wiki_url,
-            presence: true,
-            format: { with: /\A#{URI.regexp}\z/ },
-            if: :activated?
+
+  validates :external_wiki_url, presence: true, url: true, if: :activated?
 
   def title
     'External Wiki'
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 27fc19379f1d0c49e9b018f4f7f7f4c32411646c..15c7c907f7e38dac47825390cd251f7401d0f9ea 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -58,6 +58,6 @@ class FlowdockService < Service
       repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}",
       commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s",
       diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s",
-      )
+    )
   end
 end
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 91ef267ad79f11eee131fe5583f03fae602f1695..202fee042e30bfe6bbea33d1b73a673c52dc53a4 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -57,6 +57,6 @@ class GemnasiumService < Service
       token: token,
       api_key: api_key,
       repo: project.repository.path_to_repo
-      )
+    )
   end
 end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 095d04e0df4c0a2959e796d849ab583cdfa487c8..b64d97ce75dc5d2ebd90f410812b527c88a6d11d 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -18,76 +18,11 @@
 #  note_events           :boolean          default(TRUE), not null
 #
 
+# TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed
 class GitlabCiService < CiService
-  include Gitlab::Application.routes.url_helpers
-
-  after_save :compose_service_hook, if: :activated?
-  after_save :ensure_gitlab_ci_project, if: :activated?
-
-  def compose_service_hook
-    hook = service_hook || build_service_hook
-    hook.save
-  end
-
-  def ensure_gitlab_ci_project
-    project.ensure_gitlab_ci_project
-  end
-
-  def supported_events
-    %w(push tag_push)
-  end
-
-  def execute(data)
-    return unless supported_events.include?(data[:object_kind])
-
-    ci_project = project.gitlab_ci_project
-    if ci_project
-      current_user = User.find_by(id: data[:user_id])
-      Ci::CreateCommitService.new.execute(ci_project, current_user, data)
-    end
-  end
-
-  def token
-    if project.gitlab_ci_project.present?
-      project.gitlab_ci_project.token
-    end
-  end
-
-  def get_ci_commit(sha, ref)
-    Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha!(sha)
-  end
-
-  def commit_status(sha, ref)
-    get_ci_commit(sha, ref).status
-  rescue ActiveRecord::RecordNotFound
-    :error
-  end
-
-  def commit_coverage(sha, ref)
-    get_ci_commit(sha, ref).coverage
-  rescue ActiveRecord::RecordNotFound
-    :error
-  end
-
-  def build_page(sha, ref)
-    if project.gitlab_ci_project.present?
-      builds_namespace_project_commit_url(project.namespace, project, sha)
-    end
-  end
-
-  def title
-    'GitLab CI'
-  end
-
-  def description
-    'Continuous integration server from GitLab'
-  end
-
-  def to_param
-    'gitlab_ci'
-  end
-
-  def fields
-    []
+  # We override the active accessor to always make GitLabCiService disabled
+  # Otherwise the GitLabCiService can be picked, but should never be since it's deprecated
+  def active
+    false
   end
 end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index af2840a57f029dcce74014400767cbf8e6752fab..1e1686a11c627e2992349a58db5fb1eb98085868 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -22,8 +22,16 @@ class HipchatService < Service
   MAX_COMMITS = 3
 
   prop_accessor :token, :room, :server, :notify, :color, :api_version
+  boolean_accessor :notify_only_broken_builds
   validates :token, presence: true, if: :activated?
 
+  def initialize_properties
+    if properties.nil?
+      self.properties = {}
+      self.notify_only_broken_builds = true
+    end
+  end
+
   def title
     'HipChat'
   end
@@ -45,12 +53,13 @@ class HipchatService < Service
       { type: 'text', name: 'api_version',
         placeholder: 'Leave blank for default (v2)' },
       { type: 'text', name: 'server',
-        placeholder: 'Leave blank for default. https://hipchat.example.com' }
+        placeholder: 'Leave blank for default. https://hipchat.example.com' },
+      { type: 'checkbox', name: 'notify_only_broken_builds' },
     ]
   end
 
   def supported_events
-    %w(push issue merge_request note tag_push)
+    %w(push issue merge_request note tag_push build)
   end
 
   def execute(data)
@@ -94,6 +103,8 @@ class HipchatService < Service
       create_merge_request_message(data) unless is_update?(data)
     when "note"
       create_note_message(data)
+    when "build"
+      create_build_message(data) if should_build_be_notified?(data)
     end
   end
 
@@ -235,6 +246,20 @@ class HipchatService < Service
     message
   end
 
+  def create_build_message(data)
+    ref_type = data[:tag] ? 'tag' : 'branch'
+    ref = data[:ref]
+    sha = data[:sha]
+    user_name = data[:commit][:author_name]
+    status = data[:commit][:status]
+    duration = data[:commit][:duration]
+
+    branch_link = "<a href=\"#{project_url}/commits/#{URI.escape(ref)}\">#{ref}</a>"
+    commit_link = "<a href=\"#{project_url}/commit/#{URI.escape(sha)}/builds\">#{Commit.truncate_sha(sha)}</a>"
+
+    "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
+  end
+
   def project_name
     project.name_with_namespace.gsub(/\s/, '')
   end
@@ -250,4 +275,24 @@ class HipchatService < Service
   def is_update?(data)
     data[:object_attributes][:action] == 'update'
   end
+
+  def humanized_status(status)
+    case status
+    when 'success'
+      'passed'
+    else
+      status
+    end
+  end
+
+  def should_build_be_notified?(data)
+    case data[:commit][:status]
+    when 'success'
+      !notify_only_broken_builds?
+    when 'failed'
+      true
+    else
+      false
+    end
+  end
 end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 35e30b1cb0b76370baa3d5c714afbde0f2269209..e216f406e1cb672187d8dadfd2f91a375b878143 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -19,9 +19,24 @@
 #
 
 class JiraService < IssueTrackerService
+  include HTTParty
   include Gitlab::Application.routes.url_helpers
 
-  prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
+  DEFAULT_API_VERSION = 2
+
+  prop_accessor :username, :password, :api_url, :jira_issue_transition_id,
+                :title, :description, :project_url, :issues_url, :new_issue_url
+
+  before_validation :set_api_url, :set_jira_issue_transition_id
+
+  before_update :reset_password
+
+  def reset_password
+    # don't reset the password if a new one is provided
+    if api_url_changed? && !password_touched?
+      self.password = nil
+    end
+  end
 
   def help
     line1 = 'Setting `project_url`, `issues_url` and `new_issue_url` will '\
@@ -54,4 +69,228 @@ class JiraService < IssueTrackerService
   def to_param
     'jira'
   end
+
+  def fields
+    super.push(
+      { type: 'text', name: 'api_url', placeholder: 'https://jira.example.com/rest/api/2' },
+      { type: 'text', name: 'username', placeholder: '' },
+      { type: 'password', name: 'password', placeholder: '' },
+      { type: 'text', name: 'jira_issue_transition_id', placeholder: '2' }
+    )
+  end
+
+  def execute(push, issue = nil)
+    if issue.nil?
+      # No specific issue, that means
+      # we just want to test settings
+      test_settings
+    else
+      close_issue(push, issue)
+    end
+  end
+
+  def create_cross_reference_note(mentioned, noteable, author)
+    issue_name = mentioned.id
+    project = self.project
+    noteable_name = noteable.class.name.underscore.downcase
+    noteable_id = if noteable.is_a?(Commit)
+                    noteable.id
+                  else
+                    noteable.iid
+                  end
+
+    entity_url = build_entity_url(noteable_name.to_sym, noteable_id)
+
+    data = {
+      user: {
+        name: author.name,
+        url: resource_url(user_path(author)),
+      },
+      project: {
+        name: project.path_with_namespace,
+        url: resource_url(namespace_project_path(project.namespace, project))
+      },
+      entity: {
+        name: noteable_name.humanize.downcase,
+        url: entity_url
+      }
+    }
+
+    add_comment(data, issue_name)
+  end
+
+  def test_settings
+    result = JiraService.get(
+      jira_api_test_url,
+      headers: {
+        'Content-Type' => 'application/json',
+        'Authorization' => "Basic #{auth}"
+      }
+    )
+
+    case result.code
+    when 201, 200
+      Rails.logger.info("#{self.class.name} SUCCESS #{result.code}: Successfully connected to #{api_url}.")
+      true
+    else
+      Rails.logger.info("#{self.class.name} ERROR #{result.code}: #{result.parsed_response}")
+      false
+    end
+  rescue Errno::ECONNREFUSED => e
+    Rails.logger.info "#{self.class.name} ERROR: #{e.message}. API URL: #{api_url}."
+    false
+  end
+
+  private
+
+  def build_api_url_from_project_url
+    server = URI(project_url)
+    default_ports = [["http",80],["https",443]].include?([server.scheme,server.port])
+    server_url = "#{server.scheme}://#{server.host}"
+    server_url.concat(":#{server.port}") unless default_ports
+    "#{server_url}/rest/api/#{DEFAULT_API_VERSION}"
+  rescue
+    "" # looks like project URL was not valid
+  end
+
+  def set_api_url
+    self.api_url = build_api_url_from_project_url if self.api_url.blank?
+  end
+
+  def set_jira_issue_transition_id
+    self.jira_issue_transition_id ||= "2"
+  end
+
+  def close_issue(entity, issue)
+    commit_id = if entity.is_a?(Commit)
+                  entity.id
+                elsif entity.is_a?(MergeRequest)
+                  entity.last_commit.id
+                end
+    commit_url = build_entity_url(:commit, commit_id)
+
+    # Depending on the JIRA project's workflow, a comment during transition
+    # may or may not be allowed. Split the operation in to two calls so the
+    # comment always works.
+    transition_issue(issue)
+    add_issue_solved_comment(issue, commit_id, commit_url)
+  end
+
+  def transition_issue(issue)
+    message = {
+      transition: {
+        id: jira_issue_transition_id
+      }
+    }
+    send_message(close_issue_url(issue.iid), message.to_json)
+  end
+
+  def add_issue_solved_comment(issue, commit_id, commit_url)
+    comment = {
+      body: "Issue solved with [#{commit_id}|#{commit_url}]."
+    }
+
+    send_message(comment_url(issue.iid), comment.to_json)
+  end
+
+  def add_comment(data, issue_name)
+    url = comment_url(issue_name)
+    user_name = data[:user][:name]
+    user_url = data[:user][:url]
+    entity_name = data[:entity][:name]
+    entity_url = data[:entity][:url]
+    project_name = data[:project][:name]
+
+    message = {
+      body: "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]."
+    }
+
+    unless existing_comment?(issue_name, message[:body])
+      send_message(url, message.to_json)
+    end
+  end
+
+
+  def auth
+    require 'base64'
+    Base64.urlsafe_encode64("#{self.username}:#{self.password}")
+  end
+
+  def send_message(url, message)
+    result = JiraService.post(
+      url,
+      body: message,
+      headers: {
+        'Content-Type' => 'application/json',
+        'Authorization' => "Basic #{auth}"
+      }
+    )
+
+    message = case result.code
+              when 201, 200, 204
+                "#{self.class.name} SUCCESS #{result.code}: Successfully posted to #{url}."
+              when 401
+                "#{self.class.name} ERROR 401: Unauthorized. Check the #{self.username} credentials and JIRA access permissions and try again."
+              else
+                "#{self.class.name} ERROR #{result.code}: #{result.parsed_response}"
+              end
+
+    Rails.logger.info(message)
+    message
+  rescue URI::InvalidURIError, Errno::ECONNREFUSED => e
+    Rails.logger.info "#{self.class.name} ERROR: #{e.message}. Hostname: #{url}."
+  end
+
+  def existing_comment?(issue_name, new_comment)
+    result = JiraService.get(
+      comment_url(issue_name),
+      headers: {
+        'Content-Type' => 'application/json',
+        'Authorization' => "Basic #{auth}"
+      }
+    )
+
+    case result.code
+    when 201, 200
+      existing_comments = JSON.parse(result.body)['comments']
+
+      if existing_comments.present?
+        return existing_comments.map { |comment| comment['body'].include?(new_comment) }.any?
+      end
+    end
+
+    false
+  rescue JSON::ParserError
+    false
+  end
+
+  def resource_url(resource)
+    "#{Settings.gitlab['url'].chomp("/")}#{resource}"
+  end
+
+  def build_entity_url(entity_name, entity_id)
+    resource_url(
+      polymorphic_url(
+        [
+          self.project.namespace.becomes(Namespace),
+          self.project,
+          entity_name
+        ],
+        id: entity_id,
+        routing_type: :path
+      )
+    )
+  end
+
+  def close_issue_url(issue_name)
+    "#{self.api_url}/issue/#{issue_name}/transitions"
+  end
+
+  def comment_url(issue_name)
+    "#{self.api_url}/issue/#{issue_name}/comment"
+  end
+
+  def jira_api_test_url
+    "#{self.api_url}/myself"
+  end
 end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 7cd5e8925072aefbd6ac3473de7da1f4cb20b062..375b4534d07cf4c4a4937d3549b0b99062a99250 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -20,8 +20,16 @@
 
 class SlackService < Service
   prop_accessor :webhook, :username, :channel
+  boolean_accessor :notify_only_broken_builds
   validates :webhook, presence: true, if: :activated?
 
+  def initialize_properties
+    if properties.nil?
+      self.properties = {}
+      self.notify_only_broken_builds = true
+    end
+  end
+
   def title
     'Slack'
   end
@@ -45,12 +53,13 @@ class SlackService < Service
       { type: 'text', name: 'webhook',
         placeholder: 'https://hooks.slack.com/services/...' },
       { type: 'text', name: 'username', placeholder: 'username' },
-      { type: 'text', name: 'channel', placeholder: '#channel' }
+      { type: 'text', name: 'channel', placeholder: '#channel' },
+      { type: 'checkbox', name: 'notify_only_broken_builds' },
     ]
   end
 
   def supported_events
-    %w(push issue merge_request note tag_push)
+    %w(push issue merge_request note tag_push build)
   end
 
   def execute(data)
@@ -78,6 +87,8 @@ class SlackService < Service
         MergeMessage.new(data) unless is_update?(data)
       when "note"
         NoteMessage.new(data)
+      when "build"
+        BuildMessage.new(data) if should_build_be_notified?(data)
       end
 
     opt = {}
@@ -86,7 +97,7 @@ class SlackService < Service
 
     if message
       notifier = Slack::Notifier.new(webhook, opt)
-      notifier.ping(message.pretext, attachments: message.attachments)
+      notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
     end
   end
 
@@ -103,9 +114,21 @@ class SlackService < Service
   def is_update?(data)
     data[:object_attributes][:action] == 'update'
   end
+
+  def should_build_be_notified?(data)
+    case data[:commit][:status]
+    when 'success'
+      !notify_only_broken_builds?
+    when 'failed'
+      true
+    else
+      false
+    end
+  end
 end
 
 require "slack_service/issue_message"
 require "slack_service/push_message"
 require "slack_service/merge_message"
 require "slack_service/note_message"
+require "slack_service/build_message"
diff --git a/app/models/project_services/slack_service/base_message.rb b/app/models/project_services/slack_service/base_message.rb
index aa00d6061a118473758411a0b7581836ff9db291..f1182824687885584ead06828ff30ab53ebd3f28 100644
--- a/app/models/project_services/slack_service/base_message.rb
+++ b/app/models/project_services/slack_service/base_message.rb
@@ -10,6 +10,9 @@ class SlackService
       format(message)
     end
 
+    def fallback
+    end
+
     def attachments
       raise NotImplementedError
     end
diff --git a/app/models/project_services/slack_service/build_message.rb b/app/models/project_services/slack_service/build_message.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c124cad4afd04208dbbcd72ccbf5ee468ef6e4d9
--- /dev/null
+++ b/app/models/project_services/slack_service/build_message.rb
@@ -0,0 +1,82 @@
+class SlackService
+  class BuildMessage < BaseMessage
+    attr_reader :sha
+    attr_reader :ref_type
+    attr_reader :ref
+    attr_reader :status
+    attr_reader :project_name
+    attr_reader :project_url
+    attr_reader :user_name
+    attr_reader :duration
+
+    def initialize(params, commit = true)
+      @sha = params[:sha]
+      @ref_type = params[:tag] ? 'tag' : 'branch'
+      @ref = params[:ref]
+      @project_name = params[:project_name]
+      @project_url = params[:project_url]
+      @status = params[:commit][:status]
+      @user_name = params[:commit][:author_name]
+      @duration = params[:commit][:duration]
+    end
+
+    def pretext
+      ''
+    end
+
+    def fallback
+      format(message)
+    end
+
+    def attachments
+      [{ text: format(message), color: attachment_color }]
+    end
+
+    private
+
+    def message
+      "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} second(s)"
+    end
+
+    def format(string)
+      Slack::Notifier::LinkFormatter.format(string)
+    end
+
+    def humanized_status
+      case status
+      when 'success'
+        'passed'
+      else
+        status
+      end
+    end
+
+    def attachment_color
+      if status == 'success'
+        'good'
+      else
+        'danger'
+      end
+    end
+
+    def branch_url
+      "#{project_url}/commits/#{ref}"
+    end
+
+    def branch_link
+      "[#{ref}](#{branch_url})"
+    end
+
+    def project_link
+      "[#{project_name}](#{project_url})"
+    end
+
+    def commit_url
+      "#{project_url}/commit/#{sha}/builds"
+    end
+
+    def commit_link
+      "[#{Commit.truncate_sha(sha)}](#{commit_url})"
+    end
+  end
+end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 0b0224612505e18f81386b5442103193865f5d29..a63700693d778c18a99b9929c8af46116776adfb 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -23,16 +23,14 @@ class TeamcityService < CiService
 
   prop_accessor :teamcity_url, :build_type, :username, :password
 
-  validates :teamcity_url,
-    presence: true,
-    format: { with: /\A#{URI.regexp}\z/ }, if: :activated?
+  validates :teamcity_url, presence: true, url: true, if: :activated?
   validates :build_type, presence: true, if: :activated?
   validates :username,
     presence: true,
-    if: ->(service) { service.password? }, if: :activated?
+    if: ->(service) { service.activated? && service.password }
   validates :password,
     presence: true,
-    if: ->(service) { service.username? }, if: :activated?
+    if: ->(service) { service.activated? && service.username }
 
   attr_accessor :response
 
@@ -147,6 +145,6 @@ class TeamcityService < CiService
                           '</build>',
                     headers: { 'Content-type' => 'application/xml' },
                     basic_auth: auth
-        )
+                   )
   end
 end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index c1836103463dea855aea625d94de36d29e9c583b..a9bf4eb4033251fcd67261f5ebaaeca7e1e45a58 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1,7 +1,6 @@
 require 'securerandom'
 
 class Repository
-  class PreReceiveError < StandardError; end
   class CommitError < StandardError; end
 
   include Gitlab::ShellAdapter
@@ -77,7 +76,9 @@ class Repository
       path: path,
       limit: limit,
       offset: offset,
-      follow: path.present?
+      # --follow doesn't play well with --skip. See:
+      # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
+      follow: false
     }
 
     commits = Gitlab::Git::Commit.where(options)
@@ -101,17 +102,26 @@ class Repository
   end
 
   def find_branch(name)
-    branches.find { |branch| branch.name == name }
+    raw_repository.branches.find { |branch| branch.name == name }
   end
 
   def find_tag(name)
-    tags.find { |tag| tag.name == name }
+    raw_repository.tags.find { |tag| tag.name == name }
   end
 
-  def add_branch(branch_name, ref)
-    expire_branches_cache
+  def add_branch(user, branch_name, target)
+    oldrev = Gitlab::Git::BLANK_SHA
+    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+    target = commit(target).try(:id)
+
+    return false unless target
+
+    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
+      rugged.branches.create(branch_name, target)
+    end
 
-    gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
+    expire_branches_cache
+    find_branch(branch_name)
   end
 
   def add_tag(tag_name, ref, message = nil)
@@ -120,10 +130,20 @@ class Repository
     gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
   end
 
-  def rm_branch(branch_name)
+  def rm_branch(user, branch_name)
     expire_branches_cache
 
-    gitlab_shell.rm_branch(path_with_namespace, branch_name)
+    branch = find_branch(branch_name)
+    oldrev = branch.try(:target)
+    newrev = Gitlab::Git::BLANK_SHA
+    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+
+    GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
+      rugged.branches.delete(branch_name)
+    end
+
+    expire_branches_cache
+    true
   end
 
   def rm_tag(tag_name)
@@ -253,9 +273,25 @@ class Repository
 
   def license
     cache.fetch(:license) do
-      tree(:head).blobs.find do |file|
-        file.name =~ /\Alicense/i
+      licenses =  tree(:head).blobs.find_all do |file|
+                    file.name =~ /\A(copying|license|licence)/i
+                  end
+
+      preferences = [
+        /\Alicen[sc]e\z/i,        # LICENSE, LICENCE
+        /\Alicen[sc]e\./i,        # LICENSE.md, LICENSE.txt
+        /\Acopying\z/i,           # COPYING
+        /\Acopying\.(?!lesser)/i, # COPYING.txt
+        /Acopying.lesser/i        # COPYING.LESSER
+      ]
+
+      license = nil
+      preferences.each do |r|
+        license = licenses.find { |l| l.name =~ r }
+        break if license
       end
+
+      license
     end
   end
 
@@ -311,6 +347,17 @@ class Repository
     commit(sha)
   end
 
+  def next_patch_branch
+    patch_branch_ids = self.branch_names.map do |n|
+      result = n.match(/\Apatch-([0-9]+)\z/)
+      result[1].to_i if result
+    end.compact
+
+    highest_patch_branch_id = patch_branch_ids.max || 0
+
+    "patch-#{highest_patch_branch_id + 1}"
+  end
+
   # Remove archives older than 2 hours
   def branches_sorted_by(value)
     case value
@@ -547,56 +594,53 @@ class Repository
     Gitlab::Popen.popen(args, path_to_repo)
   end
 
-  def commit_with_hooks(current_user, branch)
-    oldrev = Gitlab::Git::BLANK_SHA
-    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
-    gl_id = Gitlab::ShellEnv.gl_id(current_user)
-    was_empty = empty?
-
-    # Create temporary ref
+  def with_tmp_ref(oldrev = nil)
     random_string = SecureRandom.hex
     tmp_ref = "refs/tmp/#{random_string}/head"
 
-    unless was_empty
-      oldrev = find_branch(branch).target
+    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
       rugged.references.create(tmp_ref, oldrev)
     end
 
     # Make commit in tmp ref
-    newrev = yield(tmp_ref)
+    yield(tmp_ref)
+  ensure
+    rugged.references.delete(tmp_ref) rescue nil
+  end
+
+  def commit_with_hooks(current_user, branch)
+    oldrev = Gitlab::Git::BLANK_SHA
+    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
+    was_empty = empty?
 
-    unless newrev
-      raise CommitError.new('Failed to create commit')
+    unless was_empty
+      oldrev = find_branch(branch).target
     end
 
-    # Run GitLab pre-receive hook
-    pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo)
-    status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref)
+    with_tmp_ref(oldrev) do |tmp_ref|
+      # Make commit in tmp ref
+      newrev = yield(tmp_ref)
 
-    if status
-      if was_empty
-        # Create branch
-        rugged.references.create(ref, newrev)
-      else
-        # Update head
-        current_head = find_branch(branch).target
+      unless newrev
+        raise CommitError.new('Failed to create commit')
+      end
 
-        # Make sure target branch was not changed during pre-receive hook
-        if current_head == oldrev
-          rugged.references.update(ref, newrev)
+      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
+        if was_empty
+          # Create branch
+          rugged.references.create(ref, newrev)
         else
-          raise CommitError.new('Commit was rejected because branch received new push')
+          # 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
         end
       end
-
-      # Run GitLab post receive hook
-      post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo)
-      post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
-    else
-      # Remove tmp ref and return error to user
-      rugged.references.delete(tmp_ref)
-
-      raise PreReceiveError.new('Commit was rejected by pre-receive hook')
     end
   end
 
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index 3eed5c16e45c41fec23cf93c83eba0a0d0aac01d..f36eda1531b803cabd4026f54d003d2d3f2710f7 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -17,12 +17,11 @@ class SentNotification < ActiveRecord::Base
   belongs_to :noteable, polymorphic: true
   belongs_to :recipient, class_name: "User"
 
-  validate :project, :recipient, :reply_key, presence: true
-  validate :reply_key, uniqueness: true
-
+  validates :project, :recipient, :reply_key, presence: true
+  validates :reply_key, uniqueness: true
   validates :noteable_id, presence: true, unless: :for_commit?
   validates :commit_id, presence: true, if: :for_commit?
-  validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
+  validates :line_code, line_code: true, allow_blank: true
 
   class << self
     def reply_key
diff --git a/app/models/service.rb b/app/models/service.rb
index d610abd16837ce1760d2a4585050f2b0b10f0944..d3bf7f0ebd1d16ecf1536889f696f93094929fc1 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -30,6 +30,7 @@ class Service < ActiveRecord::Base
   default_value_for :merge_requests_events, true
   default_value_for :tag_push_events, true
   default_value_for :note_events, true
+  default_value_for :build_events, true
 
   after_initialize :initialize_properties
 
@@ -40,13 +41,14 @@ class Service < ActiveRecord::Base
 
   validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
 
-  scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
+  scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) }
 
   scope :push_hooks, -> { where(push_events: true, active: true) }
   scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) }
   scope :issue_hooks, -> { where(issues_events: true, active: true) }
   scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
   scope :note_hooks, -> { where(note_events: true, active: true) }
+  scope :build_hooks, -> { where(build_events: true, active: true) }
 
   def activated?
     active
@@ -133,6 +135,21 @@ class Service < ActiveRecord::Base
     end
   end
 
+  # Provide convenient boolean accessor methods
+  # for each serialized property.
+  # Also keep track of updated properties in a similar way as ActiveModel::Dirty
+  def self.boolean_accessor(*args)
+    self.prop_accessor(*args)
+
+    args.each do |arg|
+      class_eval %{
+        def #{arg}?
+          ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
+        end
+      }
+    end
+  end
+
   # Returns a hash of the properties that have been assigned a new value since last save,
   # indicating their original values (attr => original value).
   # ActiveRecord does not provide a mechanism to track changes in serialized keys, 
@@ -163,6 +180,7 @@ class Service < ActiveRecord::Base
       assembla
       bamboo
       buildkite
+      builds_email
       campfire
       custom_issue_tracker
       drone_ci
@@ -170,7 +188,6 @@ class Service < ActiveRecord::Base
       external_wiki
       flowdock
       gemnasium
-      gitlab_ci
       hipchat
       irker
       jira
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index b0831982aa799511194ae567a68a54e47e9470ce..f876be7a4c8540ad104e1c086282def4183dd6dc 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -65,6 +65,10 @@ class Snippet < ActiveRecord::Base
     }x
   end
 
+  def self.link_reference_pattern
+    super("snippets", /(?<snippet>\d+)/)
+  end
+
   def to_reference(from_project = nil)
     reference = "#{self.class.reference_prefix}#{id}"
 
diff --git a/app/models/user.rb b/app/models/user.rb
index 9374f01f99ffc972c47cdb9a730d692410cfae57..df87f3b79bd66735c96a65eda8298d1c7fabcfe5 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -26,6 +26,7 @@
 #  bio                        :string(255)
 #  failed_attempts            :integer          default(0)
 #  locked_at                  :datetime
+#  unlock_token               :string(255)
 #  username                   :string(255)
 #  can_create_group           :boolean          default(TRUE), not null
 #  can_create_team            :boolean          default(TRUE), not null
@@ -56,6 +57,7 @@
 #  project_view               :integer          default(0)
 #  consumed_timestep          :integer
 #  layout                     :integer          default(0)
+#  hide_project_limit         :boolean          default(FALSE)
 #
 
 require 'carrierwave/orm/activerecord'
@@ -68,8 +70,10 @@ class User < ActiveRecord::Base
   include Gitlab::CurrentSettings
   include Referable
   include Sortable
-  include TokenAuthenticatable
   include CaseSensitivity
+  include TokenAuthenticatable
+
+  add_authentication_token_field :authentication_token
 
   default_value_for :admin, false
   default_value_for :can_create_group, gitlab_config.default_can_create_group
@@ -133,7 +137,7 @@ class User < ActiveRecord::Base
   has_many :assigned_merge_requests,  dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
   has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
   has_one  :abuse_report,             dependent: :destroy
-  has_many :ci_builds,                dependent: :nullify, class_name: 'Ci::Build'
+  has_many :builds,                   dependent: :nullify, class_name: 'Ci::Build'
 
 
   #
@@ -148,11 +152,9 @@ class User < ActiveRecord::Base
   validates :bio, length: { maximum: 255 }, allow_blank: true
   validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
   validates :username,
+    namespace: true,
     presence: true,
-    uniqueness: { case_sensitive: false },
-    exclusion: { in: Gitlab::Blacklist.path },
-    format: { with: Gitlab::Regex.namespace_regex,
-              message: Gitlab::Regex.namespace_regex_message }
+    uniqueness: { case_sensitive: false }
 
   validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
   validate :namespace_uniq, if: ->(user) { user.username_changed? }
@@ -219,9 +221,9 @@ class User < ActiveRecord::Base
     def find_for_database_authentication(warden_conditions)
       conditions = warden_conditions.dup
       if login = conditions.delete(:login)
-        where(conditions).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first
+        where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase)
       else
-        where(conditions).first
+        find_by(conditions)
       end
     end
 
@@ -284,7 +286,7 @@ class User < ActiveRecord::Base
     end
 
     def by_username_or_id(name_or_id)
-      where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first
+      find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i)
     end
 
     def build_user(attrs = {})
@@ -637,11 +639,11 @@ class User < ActiveRecord::Base
     email.start_with?('temp-email-for-oauth')
   end
 
-  def avatar_url(size = nil)
+  def avatar_url(size = nil, scale = 2)
     if avatar.present?
       [gitlab_config.url, avatar.url].join
     else
-      GravatarService.new.execute(email, size)
+      GravatarService.new.execute(email, size, scale)
     end
   end
 
@@ -690,7 +692,7 @@ class User < ActiveRecord::Base
   end
 
   def starred?(project)
-    starred_projects.exists?(project)
+    starred_projects.exists?(project.id)
   end
 
   def toggle_star(project)
@@ -770,10 +772,9 @@ class User < ActiveRecord::Base
 
   def ci_authorized_runners
     @ci_authorized_runners ||= begin
-      runner_ids = Ci::RunnerProject.joins(:project).
-        where("ci_projects.gitlab_id IN (#{ci_projects_union.to_sql})").
+      runner_ids = Ci::RunnerProject.
+        where("ci_runner_projects.gl_project_id IN (#{ci_projects_union.to_sql})").
         select(:runner_id)
-
       Ci::Runner.specific.where(id: runner_ids)
     end
   end
@@ -794,4 +795,9 @@ class User < ActiveRecord::Base
     Gitlab::SQL::Union.new([personal_projects.select(:id), groups.select(:id),
                             other.select(:id)])
   end
+
+  # Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
+  def send_devise_notification(notification, *args)
+    devise_mailer.send(notification, self, *args).deliver_later
+  end
 end
diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb
index 3d49cb059496e872ec63f7c312919d8e4d8659e4..413f3f485a85bed453588be0aebc870b083add16 100644
--- a/app/models/users_star_project.rb
+++ b/app/models/users_star_project.rb
@@ -10,7 +10,7 @@
 #
 
 class UsersStarProject < ActiveRecord::Base
-  belongs_to :project, counter_cache: :star_count
+  belongs_to :project, counter_cache: :star_count, touch: true
   belongs_to :user
 
   validates :user, presence: true
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index f00ec7408b683fb6f00e6c85c38ad04364bfeef3..b48ca67d4d2de1c561aedb04c307b42cdabc7903 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -39,10 +39,7 @@ class BaseService
   def deny_visibility_level(model, denied_visibility_level = nil)
     denied_visibility_level ||= model.visibility_level
 
-    level_name = 'Unknown'
-    Gitlab::VisibilityLevel.options.each do |name, level|
-      level_name = name if level == denied_visibility_level
-    end
+    level_name = Gitlab::VisibilityLevel.level_name(denied_visibility_level)
 
     model.errors.add(
       :visibility_level,
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
index 912eb6258a4aea905ee4b5ff1da73969e7a2a472..ad901f2da5ddb731ea8fc196392f44eae294c62c 100644
--- a/app/services/ci/create_builds_service.rb
+++ b/app/services/ci/create_builds_service.rb
@@ -29,9 +29,11 @@ module Ci
           build_attrs.merge!(ref: ref,
                              tag: tag,
                              trigger_request: trigger_request,
-                             user: user)
+                             user: user,
+                             project: commit.project)
 
-          commit.builds.create!(build_attrs)
+          build = commit.builds.create!(build_attrs)
+          build.execute_hooks
         end
       end
     end
diff --git a/app/services/ci/create_commit_service.rb b/app/services/ci/create_commit_service.rb
deleted file mode 100644
index 479a2d6defc5e28b722512d02d9621178f83c8bd..0000000000000000000000000000000000000000
--- a/app/services/ci/create_commit_service.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module Ci
-  class CreateCommitService
-    def execute(project, user, params)
-      sha = params[:checkout_sha] || params[:after]
-      origin_ref = params[:ref]
-      
-      unless origin_ref && sha.present?
-        return false
-      end
-
-      ref = origin_ref.gsub(/\Arefs\/(tags|heads)\//, '')
-
-      # Skip branch removal
-      if sha == Ci::Git::BLANK_SHA
-        return false
-      end
-
-      tag = origin_ref.start_with?('refs/tags/')
-      commit = project.gl_project.ensure_ci_commit(sha)
-      unless commit.skip_ci?
-        commit.update_committed!
-        commit.create_builds(ref, tag, user)
-      end
-
-      commit
-    end
-  end
-end
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index 4b86cb0a1f54edb4a6a5f7e235c0c2e728cece39..b3dfc707221b54e569042faabf5743eedbf7d29f 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -1,13 +1,13 @@
 module Ci
   class CreateTriggerRequestService
     def execute(project, trigger, ref, variables = nil)
-      commit = project.gl_project.commit(ref)
+      commit = project.commit(ref)
       return unless commit
 
       # check if ref is tag
-      tag = project.gl_project.repository.find_tag(ref).present?
+      tag = project.repository.find_tag(ref).present?
 
-      ci_commit = project.gl_project.ensure_ci_commit(commit.sha)
+      ci_commit = project.ensure_ci_commit(commit.sha)
 
       trigger_request = trigger.trigger_requests.create!(
         variables: variables,
diff --git a/app/services/ci/event_service.rb b/app/services/ci/event_service.rb
deleted file mode 100644
index 3f4e02dd26c289f8b7b7926dc5f0f63e73387f0f..0000000000000000000000000000000000000000
--- a/app/services/ci/event_service.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-module Ci
-  class EventService
-    def remove_project(user, project)
-      create(
-        description: "Project \"#{project.name}\" has been removed by #{user.username}",
-        user_id: user.id,
-        is_admin: true
-      )
-    end
-
-    def create_project(user, project)
-      create(
-        description: "Project \"#{project.name}\" has been created by #{user.username}",
-        user_id: user.id,
-        is_admin: true
-      )
-    end
-
-    def change_project_settings(user, project)
-      create(
-        project_id: project.id,
-        user_id: user.id,
-        description: "User \"#{user.username}\" updated projects settings"
-      )
-    end
-
-    def create(*args)
-      Ci::Event.create!(*args)
-    end
-  end
-end
diff --git a/app/services/ci/image_for_build_service.rb b/app/services/ci/image_for_build_service.rb
index b8d241930351e5624e872c27910e41b2b9648ed2..f469b13e90277f0be45954ab9f40543e7e5f5ebc 100644
--- a/app/services/ci/image_for_build_service.rb
+++ b/app/services/ci/image_for_build_service.rb
@@ -4,10 +4,10 @@ module Ci
       sha = params[:sha]
       sha ||=
         if params[:ref]
-          project.gl_project.commit(params[:ref]).try(:sha)
+          project.commit(params[:ref]).try(:sha)
         end
 
-      commit = project.commits.ordered.find_by(sha: sha)
+      commit = project.ci_commits.ordered.find_by(sha: sha)
       image_name = image_for_commit(commit)
 
       image_path = Rails.root.join('public/ci', image_name)
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index 7beb098659c44f70a2e865133fa64e7b85832d26..4ff268a6f067b15191e644d571a070ff44340acd 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -8,10 +8,10 @@ module Ci
       builds =
         if current_runner.shared?
           # don't run projects which have not enables shared runners
-          builds.joins(commit: { gl_project: :gitlab_ci_project }).where(ci_projects: { shared_runners_enabled: true })
+          builds.joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true })
         else
           # do run projects which are only assigned to this runner
-          builds.joins(:commit).where(ci_commits: { gl_project_id: current_runner.gl_projects_ids })
+          builds.where(project: current_runner.projects.where(builds_enabled: true))
         end
 
       builds = builds.order('created_at ASC')
@@ -20,10 +20,9 @@ module Ci
         build.can_be_served?(current_runner)
       end
 
-
       if build
         # In case when 2 runners try to assign the same build, second runner will be declined
-        # with StateMachine::InvalidTransition in run! method.
+        # with StateMachines::InvalidTransition in run! method.
         build.with_lock do
           build.runner_id = current_runner.id
           build.save!
@@ -33,7 +32,7 @@ module Ci
 
       build
 
-    rescue StateMachine::InvalidTransition
+    rescue StateMachines::InvalidTransition
       nil
     end
   end
diff --git a/app/services/ci/test_hook_service.rb b/app/services/ci/test_hook_service.rb
deleted file mode 100644
index 3a17596aaebe9954eca7cb45a98ec213789efe7d..0000000000000000000000000000000000000000
--- a/app/services/ci/test_hook_service.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module Ci
-  class TestHookService
-    def execute(hook, current_user)
-      Ci::WebHookService.new.build_end(hook.project.commits.last.last_build)
-    end
-  end
-end
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index cf7ae4345f36b85fa8d21425e7b991ce4e5a8ca4..f139872c728ec618300c9a354ff6dffa5a176d85 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -1,10 +1,10 @@
 require_relative 'base_service'
 
 class CreateBranchService < BaseService
-  def execute(branch_name, ref)
+  def execute(branch_name, ref, source_project: @project)
     valid_branch = Gitlab::GitRefValidator.validate(branch_name)
     if valid_branch == false
-      return error('Branch name invalid')
+      return error('Branch name is invalid')
     end
 
     repository = project.repository
@@ -13,8 +13,20 @@ class CreateBranchService < BaseService
       return error('Branch already exists')
     end
 
-    repository.add_branch(branch_name, ref)
-    new_branch = repository.find_branch(branch_name)
+    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
 
     if new_branch
       push_data = build_push_data(project, current_user, new_branch)
@@ -27,6 +39,8 @@ class CreateBranchService < BaseService
     else
       error('Invalid reference name')
     end
+  rescue GitHooksService::PreReceiveError
+    error('Branch creation was rejected by Git hook')
   end
 
   def success(branch)
diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..31b407efeb1fe60d0af3635ecafcc2553ef316d0
--- /dev/null
+++ b/app/services/create_commit_builds_service.rb
@@ -0,0 +1,42 @@
+class CreateCommitBuildsService
+  def execute(project, user, params)
+    return false unless project.builds_enabled?
+
+    sha = params[:checkout_sha] || params[:after]
+    origin_ref = params[:ref]
+
+    unless origin_ref && sha.present?
+      return false
+    end
+
+    ref = Gitlab::Git.ref_name(origin_ref)
+
+    # Skip branch removal
+    if sha == Gitlab::Git::BLANK_SHA
+      return false
+    end
+
+    commit = project.ci_commit(sha)
+    unless commit
+      commit = project.ci_commits.new(sha: sha)
+
+      # Skip creating ci_commit when no gitlab-ci.yml is found
+      unless commit.ci_yaml_file
+        return false
+      end
+
+      # Create a new ci_commit
+      commit.save!
+    end
+
+    # Skip creating builds for commits that have [ci skip]
+    unless commit.skip_ci?
+      # Create builds for commit
+      tag = Gitlab::Git.tag_ref?(origin_ref)
+      commit.update_committed!
+      commit.create_builds(ref, tag, user)
+    end
+
+    commit
+  end
+end
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index b19b112a0c4052560c450854dccaebc9e471c531..22bf9dd935e7122d77717c1372e86cba7e68f632 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -24,7 +24,7 @@ class DeleteBranchService < BaseService
       return error('You dont have push access to repo', 405)
     end
 
-    if repository.rm_branch(branch_name)
+    if repository.rm_branch(current_user, branch_name)
       push_data = build_push_data(branch)
 
       EventCreateService.new.push(project, current_user, push_data)
@@ -35,6 +35,8 @@ class DeleteBranchService < BaseService
     else
       error('Failed to remove branch')
     end
+  rescue GitHooksService::PreReceiveError
+    error('Branch deletion was rejected by Git hook')
   end
 
   def error(message, return_code = 400)
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 008833eed80aa9705256926ac6f07e9c8c0f08df..0326a8823e975c20a7a02825ba1b1da94f27868f 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -3,8 +3,10 @@ module Files
     class ValidationError < StandardError; end
 
     def execute
-      @current_branch = params[:current_branch]
+      @source_project = params[:source_project] || @project
+      @source_branch = params[:source_branch]
       @target_branch  = params[:target_branch]
+
       @commit_message = params[:commit_message]
       @file_path      = params[:file_path]
       @file_content   = if params[:file_content_encoding] == 'base64'
@@ -16,8 +18,8 @@ module Files
       # Validate parameters
       validate
 
-      # Create new branch if it different from current_branch
-      if @target_branch != @current_branch
+      # Create new branch if it different from source_branch
+      if different_branch?
         create_target_branch
       end
 
@@ -26,18 +28,14 @@ module Files
       else
         error("Something went wrong. Your changes were not committed")
       end
-    rescue Repository::CommitError, Repository::PreReceiveError, ValidationError => ex
+    rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, ValidationError => ex
       error(ex.message)
     end
 
     private
 
-    def current_branch
-      @current_branch ||= params[:current_branch]
-    end
-
-    def target_branch
-      @target_branch ||= params[:target_branch]
+    def different_branch?
+      @source_branch != @target_branch || @source_project != @project
     end
 
     def raise_error(message)
@@ -52,11 +50,11 @@ module Files
       end
 
       unless project.empty_repo?
-        unless repository.branch_names.include?(@current_branch)
-          raise_error("You can only create files if you are on top of a branch")
+        unless @source_project.repository.branch_names.include?(@source_branch)
+          raise_error("You can only create or edit files when you are on a branch")
         end
 
-        if @current_branch != @target_branch
+        if different_branch?
           if repository.branch_names.include?(@target_branch)
             raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes")
           end
@@ -65,10 +63,10 @@ module Files
     end
 
     def create_target_branch
-      result = CreateBranchService.new(project, current_user).execute(@target_branch, @current_branch)
+      result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project)
 
       unless result[:status] == :success
-        raise_error("Something went wrong when we tried to create #{@target_branch} for you")
+        raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}")
       end
     end
   end
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 2348920cc58c25a1900f3a40dd46e3f56dcee36f..e4cde4a2fd84e1ec3143f937a129ab4fbb13662f 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -26,7 +26,7 @@ module Files
       unless project.empty_repo?
         @file_path.slice!(0) if @file_path.start_with?('/')
 
-        blob = repository.blob_at_branch(@current_branch, @file_path)
+        blob = repository.blob_at_branch(@source_branch, @file_path)
 
         if blob
           raise_error("Your changes could not be committed because a file with the same name already exists")
diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8f5c3393dfc8e2b56fcb78817e8c15a98a4e2e63
--- /dev/null
+++ b/app/services/git_hooks_service.rb
@@ -0,0 +1,28 @@
+class GitHooksService
+  PreReceiveError = Class.new(StandardError)
+
+  def execute(user, repo_path, oldrev, newrev, ref)
+    @repo_path  = repo_path
+    @user       = Gitlab::ShellEnv.gl_id(user)
+    @oldrev     = oldrev
+    @newrev     = newrev
+    @ref        = ref
+
+    %w(pre-receive update).each do |hook_name|
+      unless run_hook(hook_name)
+        raise PreReceiveError.new("Git operation was rejected by #{hook_name} hook")
+      end
+    end
+
+    yield
+
+    run_hook('post-receive')
+  end
+
+  private
+
+  def run_hook(name)
+    hook = Gitlab::Git::Hook.new(name, @repo_path)
+    hook.trigger(@user, @oldrev, @newrev, @ref)
+  end
+end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index f11690aa3f465e1f84167202c86a0500af74c850..d7ea30bc3157e851a59c7a8b4a9a0b8a032c5976 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -61,6 +61,7 @@ class GitPushService
     EventCreateService.new.push(project, user, @push_data)
     project.execute_hooks(@push_data.dup, :push_hooks)
     project.execute_services(@push_data.dup, :push_hooks)
+    CreateCommitBuildsService.new.execute(project, @user, @push_data)
     ProjectCacheWorker.perform_async(project.id)
   end
 
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 1cc42b0b0ad21e2066f987140d612372f86ea33e..4144c7111d087c087525b50f5abe1f0eb10ef131 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -10,6 +10,7 @@ class GitTagPushService
     EventCreateService.new.push(project, user, @push_data)
     project.execute_hooks(@push_data.dup, :tag_push_hooks)
     project.execute_services(@push_data.dup, :tag_push_hooks)
+    CreateCommitBuildsService.new.execute(project, @user, @push_data)
     ProjectCacheWorker.perform_async(project.id)
 
     true
diff --git a/app/services/gravatar_service.rb b/app/services/gravatar_service.rb
index 4bee0c26a68e1e7c32c2ea91946ac8ee0c79e65a..433ecc2df32a6a66759d772271a146c87a4218b8 100644
--- a/app/services/gravatar_service.rb
+++ b/app/services/gravatar_service.rb
@@ -1,13 +1,13 @@
 class GravatarService
   include Gitlab::CurrentSettings
 
-  def execute(email, size = nil)
+  def execute(email, size = nil, scale = 2)
     if current_application_settings.gravatar_enabled? && email.present?
       size = 40 if size.nil? || size <= 0
 
       sprintf gravatar_url,
         hash: Digest::MD5.hexdigest(email.strip.downcase),
-        size: size,
+        size: size * scale,
         email: email.strip
     end
   end
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index 3d85f97b7e5a357e4c8188053e5b521f5fe156a1..a1a20e476819df6d507312edd8667da35a4a0016 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -1,6 +1,11 @@
 module Issues
   class CloseService < Issues::BaseService
     def execute(issue, commit = nil)
+      if project.jira_tracker? && project.jira_service.active
+        project.jira_service.execute(commit, issue)
+        return issue
+      end
+
       if project.default_issues_tracker? && issue.close
         event_service.close_issue(issue, current_user)
         create_note(issue, commit)
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 7963af127e1c61c92fc5361518fef0e4dd7c43bb..cabc3d8fabbf95b6b6fc7dd2ae9f57ce4d5ddfda 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -6,15 +6,12 @@ module MergeRequests
   # Executed when you do merge via GitLab UI
   #
   class MergeService < MergeRequests::BaseService
-    attr_reader :merge_request, :commit_message
+    attr_reader :merge_request
 
-    def execute(merge_request, commit_message)
-      @commit_message = commit_message
+    def execute(merge_request)
       @merge_request = merge_request
 
-      unless @merge_request.mergeable?
-        return error('Merge request is not mergeable')
-      end
+      return error('Merge request is not mergeable') unless @merge_request.mergeable?
 
       merge_request.in_locked_state do
         if commit
@@ -32,13 +29,13 @@ module MergeRequests
       committer = repository.user_to_committer(current_user)
 
       options = {
-        message: commit_message,
+        message: params[:commit_message] || merge_request.merge_commit_message,
         author: committer,
         committer: committer
       }
 
       repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options)
-    rescue Exception => e
+    rescue StandardError => e
       merge_request.update(merge_error: "Something went wrong during merge")
       Rails.logger.error(e.message)
       return false
@@ -46,6 +43,11 @@ module MergeRequests
 
     def after_merge
       MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
+
+      if params[:should_remove_source_branch]
+        DeleteBranchService.new(@merge_request.source_project, current_user).
+          execute(merge_request.source_branch)
+      end
     end
   end
 end
diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5cf7404a4936aafc19b561cd64339c3bef5b4044
--- /dev/null
+++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb
@@ -0,0 +1,55 @@
+module MergeRequests
+  class MergeWhenBuildSucceedsService < MergeRequests::BaseService
+    # Marks the passed `merge_request` to be merged when the build succeeds or
+    # updates the params for the automatic merge
+    def execute(merge_request)
+      merge_request.merge_params.merge!(params)
+
+      # The service is also called when the merge params are updated.
+      already_approved = merge_request.merge_when_build_succeeds?
+
+      unless already_approved
+        merge_request.merge_when_build_succeeds = true
+        merge_request.merge_user                = @current_user
+
+        SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.last_commit)
+      end
+
+      merge_request.save
+    end
+
+    # Triggers the automatic merge of merge_request once the build succeeds
+    def trigger(build)
+      merge_requests = merge_request_from(build)
+
+      merge_requests.each do |merge_request|
+        next unless merge_request.merge_when_build_succeeds?
+
+        if merge_request.ci_commit && merge_request.ci_commit.success? && merge_request.mergeable?
+          MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
+        end
+      end
+    end
+
+    # Cancels the automatic merge
+    def cancel(merge_request)
+      if merge_request.merge_when_build_succeeds? && merge_request.open?
+        merge_request.reset_merge_when_build_succeeds
+        SystemNoteService.cancel_merge_when_build_succeeds(merge_request, @project, @current_user)
+
+        success
+      else
+        error("Can't cancel the automatic merge", 406)
+      end
+    end
+
+    private
+
+    def merge_request_from(build)
+      merge_requests = @project.origin_merge_requests.opened.where(source_branch: build.ref).to_a
+      merge_requests += @project.fork_merge_requests.opened.where(source_branch: build.ref).to_a
+
+      merge_requests.uniq.select(&:source_project)
+    end
+  end
+end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index e180edb4bf3b2f7609748cc36c05ac06a44de2fb..8b3d56c2b4c1b574d496934cc71c82b35faa9d57 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -11,6 +11,7 @@ module MergeRequests
       # empty diff during a manual merge
       close_merge_requests
       reload_merge_requests
+      reset_merge_when_build_succeeds
 
       # Leave a system note if a branch was deleted/added
       if branch_added? || branch_removed?
@@ -57,7 +58,6 @@ module MergeRequests
       merge_requests = filter_merge_requests(merge_requests)
 
       merge_requests.each do |merge_request|
-
         if merge_request.source_branch == @branch_name || force_push?
           merge_request.reload_code
           merge_request.mark_as_unchecked
@@ -76,6 +76,10 @@ module MergeRequests
       end
     end
 
+    def reset_merge_when_build_succeeds
+      merge_requests_for_source_branch.each(&:reset_merge_when_build_succeeds)
+    end
+
     def find_new_commits
       if branch_added?
         @commits = []
@@ -108,7 +112,7 @@ module MergeRequests
 
       merge_requests_for_source_branch.each do |merge_request|
         SystemNoteService.change_branch_presence(
-            merge_request, merge_request.project, @current_user,
+          merge_request, merge_request.project, @current_user,
             :source, @branch_name, presence)
       end
     end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index dbff58dfb9c78564d7e20ddb5613c4eafe93effd..a8486e6a5a1ecb3c62f7d507e666d9eee9e2943a 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,11 +5,6 @@ module Notes
       note.author = current_user
       note.system = false
 
-      if contains_emoji_only?(params[:note])
-        note.is_award = true
-        note.note = emoji_name(params[:note])
-      end
-
       if note.save
         notification_service.new_note(note)
 
@@ -33,13 +28,5 @@ module Notes
       note.project.execute_hooks(note_data, :note_hooks)
       note.project.execute_services(note_data, :note_hooks)
     end
-
-    def contains_emoji_only?(note)
-      note =~ /\A:[-_+[:alnum:]]*:\s?\z/
-    end
-
-    def emoji_name(note)
-      note.match(/\A:([-_+[:alnum:]]*):\s?/)[1]
-    end
   end
 end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index d6550fbb555e65c1f30e4551d0e45ab5f3c2d3ae..bdf7b3ad2bb58f654ba61d33dd1a84e6159910c8 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -13,14 +13,14 @@ class NotificationService
   # even if user disabled notifications
   def new_key(key)
     if key.user
-      mailer.new_ssh_key_email(key.id)
+      mailer.new_ssh_key_email(key.id).deliver_later
     end
   end
 
   # Always notify user about email added to profile
   def new_email(email)
     if email.user
-      mailer.new_email_email(email.id)
+      mailer.new_email_email(email.id).deliver_later
     end
   end
 
@@ -79,17 +79,27 @@ class NotificationService
   end
 
   def merge_mr(merge_request, current_user)
-    close_resource_email(merge_request, merge_request.target_project, current_user, 'merged_merge_request_email')
+    close_resource_email(
+      merge_request,
+      merge_request.target_project,
+      current_user,
+      'merged_merge_request_email'
+    )
   end
 
   def reopen_mr(merge_request, current_user)
-    reopen_resource_email(merge_request, merge_request.target_project, current_user, 'merge_request_status_email', 'reopened')
+    reopen_resource_email(
+      merge_request,
+      merge_request.target_project,
+      current_user, 'merge_request_status_email',
+      'reopened'
+    )
   end
 
   # Notify new user with email after creation
   def new_user(user, token = nil)
     # Don't email omniauth created users
-    mailer.new_user_email(user.id, token) unless user.identities.any?
+    mailer.new_user_email(user.id, token).deliver_later unless user.identities.any?
   end
 
   # Notify users on new note in system
@@ -135,53 +145,63 @@ class NotificationService
     recipients = reject_unsubscribed_users(recipients, note.noteable)
 
     recipients.delete(note.author)
+    recipients = recipients.uniq
 
     # build notify method like 'note_commit_email'
     notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
-
     recipients.each do |recipient|
-      mailer.send(notify_method, recipient.id, note.id)
+      mailer.send(notify_method, recipient.id, note.id).deliver_later
     end
   end
 
   def invite_project_member(project_member, token)
-    mailer.project_member_invited_email(project_member.id, token)
+    mailer.project_member_invited_email(project_member.id, token).deliver_later
   end
 
   def accept_project_invite(project_member)
-    mailer.project_invite_accepted_email(project_member.id)
+    mailer.project_invite_accepted_email(project_member.id).deliver_later
   end
 
   def decline_project_invite(project_member)
-    mailer.project_invite_declined_email(project_member.project.id, project_member.invite_email, project_member.access_level, project_member.created_by_id)
+    mailer.project_invite_declined_email(
+      project_member.project.id,
+      project_member.invite_email,
+      project_member.access_level,
+      project_member.created_by_id
+    ).deliver_later
   end
 
   def new_project_member(project_member)
-    mailer.project_access_granted_email(project_member.id)
+    mailer.project_access_granted_email(project_member.id).deliver_later
   end
 
   def update_project_member(project_member)
-    mailer.project_access_granted_email(project_member.id)
+    mailer.project_access_granted_email(project_member.id).deliver_later
   end
 
   def invite_group_member(group_member, token)
-    mailer.group_member_invited_email(group_member.id, token)
+    mailer.group_member_invited_email(group_member.id, token).deliver_later
   end
 
   def accept_group_invite(group_member)
-    mailer.group_invite_accepted_email(group_member.id)
+    mailer.group_invite_accepted_email(group_member.id).deliver_later
   end
 
   def decline_group_invite(group_member)
-    mailer.group_invite_declined_email(group_member.group.id, group_member.invite_email, group_member.access_level, group_member.created_by_id)
+    mailer.group_invite_declined_email(
+      group_member.group.id,
+      group_member.invite_email,
+      group_member.access_level,
+      group_member.created_by_id
+    ).deliver_later
   end
 
   def new_group_member(group_member)
-    mailer.group_access_granted_email(group_member.id)
+    mailer.group_access_granted_email(group_member.id).deliver_later
   end
 
   def update_group_member(group_member)
-    mailer.group_access_granted_email(group_member.id)
+    mailer.group_access_granted_email(group_member.id).deliver_later
   end
 
   def project_was_moved(project, old_path_with_namespace)
@@ -189,7 +209,11 @@ class NotificationService
     recipients = reject_muted_users(recipients, project)
 
     recipients.each do |recipient|
-      mailer.project_was_moved_email(project.id, recipient.id, old_path_with_namespace)
+      mailer.project_was_moved_email(
+        project.id,
+        recipient.id,
+        old_path_with_namespace
+      ).deliver_later
     end
   end
 
@@ -339,7 +363,7 @@ class NotificationService
     recipients = build_recipients(target, project, target.author)
 
     recipients.each do |recipient|
-      mailer.send(method, recipient.id, target.id)
+      mailer.send(method, recipient.id, target.id).deliver_later
     end
   end
 
@@ -347,7 +371,7 @@ class NotificationService
     recipients = build_recipients(target, project, current_user)
 
     recipients.each do |recipient|
-      mailer.send(method, recipient.id, target.id, current_user.id)
+      mailer.send(method, recipient.id, target.id, current_user.id).deliver_later
     end
   end
 
@@ -358,7 +382,13 @@ class NotificationService
     recipients = build_recipients(target, project, current_user, [previous_assignee])
 
     recipients.each do |recipient|
-      mailer.send(method, recipient.id, target.id, previous_assignee_id, current_user.id)
+      mailer.send(
+        method,
+        recipient.id,
+        target.id,
+        previous_assignee_id,
+        current_user.id
+      ).deliver_later
     end
   end
 
@@ -366,7 +396,7 @@ class NotificationService
     recipients = build_recipients(target, project, current_user)
 
     recipients.each do |recipient|
-      mailer.send(method, recipient.id, target.id, status, current_user.id)
+      mailer.send(method, recipient.id, target.id, status, current_user.id).deliver_later
     end
   end
 
@@ -388,7 +418,7 @@ class NotificationService
   end
 
   def mailer
-    Notify.delay
+    Notify
   end
 
   def previous_record(object, attribute)
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 700a1db04d83ecf78f19fa5e1b0021eefc392be7..a6820183bee9ed83a3d94a3b9cb20933f9a00ec8 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -64,8 +64,10 @@ module Projects
       after_create_actions if @project.persisted?
 
       @project
-    rescue
-      @project.errors.add(:base, "Can't save project. Please try again later")
+    rescue => e
+      message = "Unable to save project: #{e.message}"
+      Rails.logger.error(message)
+      @project.errors.add(:base, message) if @project
       @project
     end
 
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index 5da1c7afd924dfcf19f106320987dfa1f0257b39..0577ae778d54d176f57494e0101bdf3d2c916cc5 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -7,6 +7,8 @@ module Projects
         description:            @project.description,
         name:                   @project.name,
         path:                   @project.path,
+        shared_runners_enabled: @project.shared_runners_enabled,
+        builds_enabled:         @project.builds_enabled,
         namespace_id:           @params[:namespace].try(:id) || current_user.namespace.id
       }
 
@@ -15,19 +17,6 @@ module Projects
       end
 
       new_project = CreateService.new(current_user, new_params).execute
-
-      if new_project.persisted?
-        if @project.builds_enabled?
-          new_project.enable_ci
-
-          settings = @project.gitlab_ci_project.attributes.select do |attr_name, value|
-            ["public", "shared_runners_enabled", "allow_git_fetch"].include? attr_name
-          end
-
-          new_project.gitlab_ci_project.update(settings)
-        end
-      end
-
       new_project
     end
   end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 69bdd045ddfeece023c92821cd7d7cdb3cd89853..895e089bea3c74f47cb41dd811efe5730cc09b5b 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -3,12 +3,16 @@ 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)
-          deny_visibility_level(project, new_visibility)
-          return project
+      if new_visibility
+        if new_visibility.to_i != project.visibility_level
+          unless can?(current_user, :change_visibility_level, project) &&
+            Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
+            deny_visibility_level(project, new_visibility)
+            return project
+          end
         end
+
+        return false unless visibility_level_allowed?(new_visibility)
       end
 
       new_branch = params[:default_branch]
@@ -23,5 +27,19 @@ module Projects
         end
       end
     end
+
+    private
+
+    def visibility_level_allowed?(level)
+      return true if project.visibility_level_allowed?(level)
+
+      level_name = Gitlab::VisibilityLevel.level_name(level)
+      project.errors.add(
+        :visibility_level,
+        "#{level_name} could not be set as visibility level of this project - parent project settings are more restrictive"
+      )
+
+      false
+    end
   end
 end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 7e2bc834176a7b832ccf183a4f03cfa09253f074..98a71cbf1ada720854f7627ce00206d709b5206d 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -125,7 +125,21 @@ class SystemNoteService
   # Returns the created Note object
   def self.change_status(noteable, project, author, status, source)
     body = "Status changed to #{status}"
-    body += " by #{source.gfm_reference}" if source
+    body += " by #{source.gfm_reference(project)}" if source
+
+    create_note(noteable: noteable, project: project, author: author, note: body)
+  end
+
+  # Called when 'merge when build succeeds' is executed
+  def self.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)
+    body = "Canceled the automatic merge"
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
@@ -227,9 +241,14 @@ class SystemNoteService
       note_options.merge!(noteable: noteable)
     end
 
-    create_note(note_options)
+    if noteable.is_a?(ExternalIssue)
+      noteable.project.issues_tracker.create_cross_reference_note(noteable, mentioner, author)
+    else
+      create_note(note_options)
+    end
   end
 
+
   def self.cross_reference?(note_text)
     note_text.start_with?(cross_reference_note_prefix)
   end
@@ -245,7 +264,7 @@ class SystemNoteService
   #
   # Returns Boolean
   def self.cross_reference_disallowed?(noteable, mentioner)
-    return true if noteable.is_a?(ExternalIssue)
+    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)
 
diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb
index b4e0fc5772dc101fdd760c3e1a95d6ec96cf6aca..1dccc39e7e2e4d5902948453b71e19aab275866c 100644
--- a/app/uploaders/artifact_uploader.rb
+++ b/app/uploaders/artifact_uploader.rb
@@ -5,15 +5,15 @@ class ArtifactUploader < CarrierWave::Uploader::Base
   attr_accessor :build, :field
 
   def self.artifacts_path
-    File.expand_path('shared/artifacts/', Rails.root)
+    Gitlab.config.artifacts.path
   end
 
   def self.artifacts_upload_path
-    File.expand_path('shared/artifacts/tmp/uploads/', Rails.root)
+    File.join(self.artifacts_path, 'tmp/uploads/')
   end
 
   def self.artifacts_cache_path
-    File.expand_path('shared/artifacts/tmp/cache/', Rails.root)
+    File.join(self.artifacts_path, 'tmp/cache/')
   end
 
   def initialize(build, field)
diff --git a/app/validators/color_validator.rb b/app/validators/color_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..571d0007aa2d111bf6bd4416584e5493f19d78f5
--- /dev/null
+++ b/app/validators/color_validator.rb
@@ -0,0 +1,20 @@
+# ColorValidator
+#
+# Custom validator for web color codes. It requires the leading hash symbol and
+# will accept RGB triplet or hexadecimal formats.
+#
+# Example:
+#
+#   class User < ActiveRecord::Base
+#     validates :background_color, allow_blank: true, color: true
+#   end
+#
+class ColorValidator < ActiveModel::EachValidator
+  PATTERN = /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/.freeze
+
+  def validate_each(record, attribute, value)
+    unless value =~ PATTERN
+      record.errors.add(attribute, "must be a valid color code")
+    end
+  end
+end
diff --git a/lib/email_validator.rb b/app/validators/email_validator.rb
similarity index 63%
rename from lib/email_validator.rb
rename to app/validators/email_validator.rb
index f509f0a584336fc0b3a1a5cccd36477eeb4c7fd4..b35af10080399cd8358170ca2d97c9514196d4d2 100644
--- a/lib/email_validator.rb
+++ b/app/validators/email_validator.rb
@@ -1,3 +1,5 @@
+# EmailValidator
+#
 # Based on https://github.com/balexand/email_validator
 #
 # Extended to use only strict mode with following allowed characters:
@@ -6,15 +8,10 @@
 # See http://www.remote.org/jochen/mail/info/chars.html
 #
 class EmailValidator < ActiveModel::EachValidator
-  @@default_options = {}
-
-  def self.default_options
-    @@default_options
-  end
+  PATTERN = /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i.freeze
 
   def validate_each(record, attribute, value)
-    options = @@default_options.merge(self.options)
-    unless value =~ /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i
+    unless value =~ PATTERN
       record.errors.add(attribute, options[:message] || :invalid)
     end
   end
diff --git a/app/validators/line_code_validator.rb b/app/validators/line_code_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ed29e5aeb67f08f3e401db4c0f5f815ecf4766b9
--- /dev/null
+++ b/app/validators/line_code_validator.rb
@@ -0,0 +1,12 @@
+# LineCodeValidator
+#
+# Custom validator for GitLab line codes.
+class LineCodeValidator < ActiveModel::EachValidator
+  PATTERN = /\A[a-z0-9]+_\d+_\d+\z/.freeze
+
+  def validate_each(record, attribute, value)
+    unless value =~ PATTERN
+      record.errors.add(attribute, "must be a valid line code")
+    end
+  end
+end
diff --git a/app/validators/namespace_name_validator.rb b/app/validators/namespace_name_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2e51af2982d59035420e458e50e073f27153e645
--- /dev/null
+++ b/app/validators/namespace_name_validator.rb
@@ -0,0 +1,10 @@
+# NamespaceNameValidator
+#
+# Custom validator for GitLab namespace name strings.
+class NamespaceNameValidator < ActiveModel::EachValidator
+  def validate_each(record, attribute, value)
+    unless value =~ Gitlab::Regex.namespace_name_regex
+      record.errors.add(attribute, Gitlab::Regex.namespace_name_regex_message)
+    end
+  end
+end
diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..10e35ce665ae61233eba90b7d1c534fbcf5d6aef
--- /dev/null
+++ b/app/validators/namespace_validator.rb
@@ -0,0 +1,50 @@
+# NamespaceValidator
+#
+# Custom validator for GitLab namespace values.
+#
+# Values are checked for formatting and exclusion from a list of reserved path
+# names.
+class NamespaceValidator < ActiveModel::EachValidator
+  RESERVED = %w(
+    admin
+    all
+    assets
+    ci
+    dashboard
+    files
+    groups
+    help
+    hooks
+    issues
+    merge_requests
+    notes
+    profile
+    projects
+    public
+    repository
+    s
+    search
+    services
+    snippets
+    teams
+    u
+    unsubscribes
+    users
+  ).freeze
+
+  def validate_each(record, attribute, value)
+    unless value =~ Gitlab::Regex.namespace_regex
+      record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
+    end
+
+    if reserved?(value)
+      record.errors.add(attribute, "#{value} is a reserved name")
+    end
+  end
+
+  private
+
+  def reserved?(value)
+    RESERVED.include?(value)
+  end
+end
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2848b9cd33d1603fd5860315f99dbe961744ad21
--- /dev/null
+++ b/app/validators/url_validator.rb
@@ -0,0 +1,36 @@
+# UrlValidator
+#
+# Custom validator for URLs.
+#
+# By default, only URLs for the HTTP(S) protocols will be considered valid.
+# Provide a `:protocols` option to configure accepted protocols.
+#
+# Example:
+#
+#   class User < ActiveRecord::Base
+#     validates :personal_url, url: true
+#
+#     validates :ftp_url, url: { protocols: %w(ftp) }
+#
+#     validates :git_url, url: { protocols: %w(http https ssh git) }
+#   end
+#
+class UrlValidator < ActiveModel::EachValidator
+  def validate_each(record, attribute, value)
+    unless valid_url?(value)
+      record.errors.add(attribute, "must be a valid URL")
+    end
+  end
+
+  private
+
+  def default_options
+    @default_options ||= { protocols: %w(http https) }
+  end
+
+  def valid_url?(value)
+    options = default_options.merge(self.options)
+
+    value =~ /\A#{URI.regexp(options[:protocols])}\z/
+  end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index ddaf0e0e8ffae6b79185aa49caa2f9a9a6e55d98..214e0209bb7e31c5bf28e52a0f8c7f62c74d13b6 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -14,11 +14,11 @@
     .form-group.project-visibility-level-holder
       = f.label :default_project_visibility, class: 'control-label col-sm-2'
       .col-sm-10
-        = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: 'Project')
+        = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project)
     .form-group.project-visibility-level-holder
       = f.label :default_snippet_visibility, class: 'control-label col-sm-2'
       .col-sm-10
-        = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: 'Snippet')
+        = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: PersonalSnippet)
     .form-group
       = f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
       .col-sm-10
@@ -104,6 +104,18 @@
           = f.label :signin_enabled do
             = f.check_box :signin_enabled
             Sign-in enabled
+    .form-group
+      = f.label :two_factor_authentication, 'Two-Factor authentication', class: 'control-label col-sm-2'
+      .col-sm-10
+        .checkbox
+          = f.label :require_two_factor_authentication do
+            = f.check_box :require_two_factor_authentication
+            Require all users to setup Two-Factor authentication
+    .form-group
+      = f.label :two_factor_authentication, 'Two-Factor grace period (hours)', class: 'control-label col-sm-2'
+      .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
@@ -144,5 +156,82 @@
       .col-sm-10
         = f.number_field :max_artifacts_size, class: 'form-control'
 
+  %fieldset
+    %legend Metrics
+    %p
+      These settings require a restart to take effect.
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :metrics_enabled do
+            = f.check_box :metrics_enabled
+            Enable InfluxDB Metrics
+    .form-group
+      = f.label :metrics_host, 'InfluxDB host', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :metrics_host, class: 'form-control', placeholder: 'influxdb.example.com'
+    .form-group
+      = f.label :metrics_port, 'InfluxDB port', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :metrics_port, class: 'form-control', placeholder: '8089'
+        .help-block
+          The UDP port to use for connecting to InfluxDB. InfluxDB requires that
+          your server configuration specifies a database to store data in when
+          sending messages to this port, without it metrics data will not be
+          saved.
+    .form-group
+      = f.label :metrics_username, 'InfluxDB username', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :metrics_username, class: 'form-control'
+    .form-group
+      = f.label :metrics_password, 'InfluxDB password', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :metrics_password, class: 'form-control'
+    .form-group
+      = f.label :metrics_pool_size, 'Connection pool size', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :metrics_pool_size, class: 'form-control'
+        .help-block
+          The amount of InfluxDB connections to open. Connections are opened
+          lazily. Users using multi-threaded application servers should ensure
+          enough connections are available (at minimum the amount of application
+          server threads).
+    .form-group
+      = f.label :metrics_timeout, 'Connection timeout', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :metrics_timeout, class: 'form-control'
+        .help-block
+          The amount of seconds after which an InfluxDB connection will time
+          out.
+    .form-group
+      = f.label :metrics_method_call_threshold, 'Method Call Threshold (ms)', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.number_field :metrics_method_call_threshold, class: 'form-control'
+        .help-block
+          A method call is only tracked when it takes longer to complete than
+          the given amount of milliseconds.
+
+  %fieldset
+    %legend Spam and Anti-bot Protection
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :recaptcha_enabled do
+            = f.check_box :recaptcha_enabled
+            Enable reCAPTCHA
+          %span.help-block#recaptcha_help_block Helps preventing bots from creating accounts
+
+    .form-group
+      = f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :recaptcha_site_key, class: 'form-control'
+        .help-block
+          Generate site and private keys here:
+          %a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha
+    .form-group
+      = f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :recaptcha_private_key, class: 'form-control'
+
   .form-actions
     = f.submit 'Save', class: 'btn btn-primary'
diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..6936e614346a3d9dbee1561c671875fe4310b5bc
--- /dev/null
+++ b/app/views/admin/builds/_build.html.haml
@@ -0,0 +1,73 @@
+- project = build.project
+%tr.build
+  %td.status
+    = ci_status_with_icon(build.status)
+
+  %td.build-link
+    - if build.target_url
+      = link_to build.target_url do
+        %strong Build ##{build.id}
+    - else
+      %strong Build ##{build.id}
+
+    - if build.show_warning?
+      %i.fa.fa-warning.text-warning
+
+  %td
+    - if project
+      = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project), class: "monospace"
+
+  %td
+    = link_to build.short_sha, namespace_project_commit_path(project.namespace, project, build.sha), class: "monospace"
+
+  %td
+    - if build.ref
+      = link_to build.ref, namespace_project_commits_path(project.namespace, project, build.ref)
+    - else
+      .light none
+
+  %td
+    - if build.try(:runner)
+      = runner_link(build.runner)
+    - else
+      .light none
+
+  %td
+    #{build.stage} / #{build.name}
+
+    .pull-right
+      - if build.tags.any?
+        - build.tags.each do |tag|
+          %span.label.label-primary
+            = tag
+      - if build.try(:trigger_request)
+        %span.label.label-info triggered
+      - if build.try(:allow_failure)
+        %span.label.label-danger allowed to fail
+
+  %td.duration
+    - if build.duration
+      #{duration_in_words(build.finished_at, build.started_at)}
+
+  %td.timestamp
+    - if build.finished_at
+      %span #{time_ago_with_tooltip(build.finished_at)}
+
+  - if defined?(coverage) && coverage
+    %td.coverage
+      - if build.try(:coverage)
+        #{build.coverage}%
+
+  %td
+    .pull-right
+      - if current_user && can?(current_user, :download_build_artifacts, project) && build.download_url
+        = link_to build.download_url, title: 'Download artifacts' do
+          %i.fa.fa-download
+      - if current_user && can?(current_user, :manage_builds, build.project)
+        - if build.active?
+          - if build.cancel_url
+            = link_to build.cancel_url, method: :post, title: 'Cancel' do
+              %i.fa.fa-remove.cred
+        - elsif defined?(allow_retry) && allow_retry && build.retry_url
+          = link_to build.retry_url, method: :post, title: 'Retry' do
+            %i.fa.fa-repeat
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..55da06a7fe9555f96c6a82a9dc1d659a4ee5c038
--- /dev/null
+++ b/app/views/admin/builds/index.html.haml
@@ -0,0 +1,50 @@
+.project-issuable-filter
+  .controls
+    .pull-left.hidden-xs
+      - if @all_builds.running_or_pending.any?
+        = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
+
+  %ul.center-top-menu
+    %li{class: ('active' if @scope.nil?)}
+      = link_to admin_builds_path do
+        Running
+        %span.badge.js-running-count= @all_builds.running_or_pending.count(:id)
+
+    %li{class: ('active' if @scope == 'finished')}
+      = link_to admin_builds_path(scope: :finished) do
+        Finished
+        %span.badge.js-running-count= @all_builds.finished.count(:id)
+
+    %li{class: ('active' if @scope == 'all')}
+      = link_to admin_builds_path(scope: :all) do
+        All
+        %span.badge.js-totalbuilds-count= @all_builds.count(:id)
+
+.gray-content-block
+  #{(@scope || 'running').capitalize} builds
+
+%ul.content-list
+  - if @builds.blank?
+    %li
+      .nothing-here-block No builds to show
+  - else
+    .table-holder
+      %table.table.builds
+        %thead
+          %tr
+            %th Status
+            %th Build ID
+            %th Project
+            %th Commit
+            %th Ref
+            %th Runner
+            %th Name
+            %th Duration
+            %th Finished at
+            %th
+
+        - @builds.each do |build|
+          = render "admin/builds/build", build: build
+
+    = paginate @builds, theme: 'gitlab'
+
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 8657d2c71fe83eae1a39a5b521ca922acbbdca4c..531247e9148565a0e3b475a435c5a5bd709858bb 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -79,6 +79,10 @@
         GitLab API
         %span.pull-right
           = API::API::version
+      %p
+        Git
+        %span.pull-right
+          = Gitlab::Git.version
       %p
         Ruby
         %span.pull-right
diff --git a/app/views/admin/identities/index.html.haml b/app/views/admin/identities/index.html.haml
index 8358a14445b24c20eaa0e7edc661f42fd1187c32..741d111fb7d40c3db8e27f3127784eefc652e345 100644
--- a/app/views/admin/identities/index.html.haml
+++ b/app/views/admin/identities/index.html.haml
@@ -1,6 +1,7 @@
 - page_title "Identities", @user.name, "Users"
 = render 'admin/users/head'
 
+= link_to 'New Identity', new_admin_user_identity_path, class: 'pull-right btn btn-new'
 - if @identities.present?
   .table-holder
     %table.table
diff --git a/app/views/admin/identities/new.html.haml b/app/views/admin/identities/new.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e30bf0ef0ee23fd90f03946282c2ecf315e4b316
--- /dev/null
+++ b/app/views/admin/identities/new.html.haml
@@ -0,0 +1,4 @@
+- page_title "New Identity"
+%h3.page-title New identity
+%hr
+= render 'form'
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
index a5ace4e7a3bd2584f1a84d50140bf7289f246eb0..eaa94ed9e3631b5e4b4cd9a011dd1f31a50cd90d 100644
--- a/app/views/admin/labels/_form.html.haml
+++ b/app/views/admin/labels/_form.html.haml
@@ -12,7 +12,7 @@
     .col-sm-10
       = f.text_field :title, class: "form-control", required: true
   .form-group
-    = f.label :color, "Background Color", class: 'control-label'
+    = f.label :color, "Background color", class: 'control-label'
     .col-sm-10
       .input-group
         .input-group-addon.label-color-preview &nbsp;
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
index 596e06243ddc6a7f9c8fe5691da45a27341e95a7..e3ccbf6c3a84e6d7896b386dfe3c7a482c7cab67 100644
--- a/app/views/admin/labels/_label.html.haml
+++ b/app/views/admin/labels/_label.html.haml
@@ -2,4 +2,4 @@
   = render_colored_label(label)
   .pull-right
     = link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm'
-    = link_to 'Remove', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
+    = link_to 'Delete', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Delete this label? Are you sure?"}
diff --git a/app/views/admin/labels/edit.html.haml b/app/views/admin/labels/edit.html.haml
index 45c62a76259d6871fbcb20e4637665496cea71cc..309aedceded99c37f5277535054ade4716835f8e 100644
--- a/app/views/admin/labels/edit.html.haml
+++ b/app/views/admin/labels/edit.html.haml
@@ -1,9 +1,5 @@
 - page_title "Edit", @label.name, "Labels"
-%h3
-  Edit label
-  %span.light #{@label.name}
-.back-link
-  = link_to admin_labels_path do
-    &larr; To labels list
+%h3.page-title
+  Edit Label
 %hr
 = render 'form'
diff --git a/app/views/admin/labels/new.html.haml b/app/views/admin/labels/new.html.haml
index 8d298ad20f71d0c3d858b41e155d53107b65cae9..0135ad0723d9030052403cec902eebae070a14d3 100644
--- a/app/views/admin/labels/new.html.haml
+++ b/app/views/admin/labels/new.html.haml
@@ -1,7 +1,5 @@
 - page_title "New Label"
-%h3 New label
-.back-link
-  = link_to admin_labels_path do
-    &larr; To labels list
+%h3.page-title
+  New Label
 %hr
 = render 'form'
diff --git a/app/views/ci/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
similarity index 62%
rename from app/views/ci/admin/runners/_runner.html.haml
rename to app/views/admin/runners/_runner.html.haml
index 701782d26bb8627d3f1fd02583af1bdd596dc3a9..6745e58deca5f6f127bff5fc79cc7c1e0d5f2655 100644
--- a/app/views/ci/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -8,14 +8,14 @@
       %span.label.label-danger paused
 
   %td
-    = link_to ci_admin_runner_path(runner) do
+    = link_to admin_runner_path(runner) do
       = runner.short_sha
   %td
     .runner-description
       = runner.description
       %span (#{link_to 'edit', '#', class: 'edit-runner-link'})
     .runner-description-form.hide
-      = form_for [:ci, :admin, runner], remote: true, html: { class: 'form-inline' } do |f|
+      = form_for [:admin, runner], remote: true, html: { class: 'form-inline' } do |f|
         .form-group
           = f.text_field :description, class: 'form-control'
         = f.submit 'Save', class: 'btn'
@@ -38,11 +38,11 @@
       Never
   %td
     .pull-right
-      = link_to 'Edit', ci_admin_runner_path(runner), class: 'btn btn-sm'
+      = link_to 'Edit', admin_runner_path(runner), class: 'btn btn-sm'
       &nbsp;
       - if runner.active?
-        = link_to 'Pause', [:pause, :ci, :admin, runner], data: { confirm: "Are you sure?" }, method: :get, class: 'btn btn-danger btn-sm'
+        = link_to 'Pause', [:pause, :admin, runner], data: { confirm: "Are you sure?" }, method: :get, class: 'btn btn-danger btn-sm'
       - else
-        = link_to 'Resume', [:resume, :ci, :admin, runner], method: :get, class: 'btn btn-success btn-sm'
-      = link_to 'Remove', [:ci, :admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
+        = link_to 'Resume', [:resume, :admin, runner], method: :get, class: 'btn btn-success btn-sm'
+      = link_to 'Remove', [:admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
 
diff --git a/app/views/ci/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
similarity index 58%
rename from app/views/ci/admin/runners/index.html.haml
rename to app/views/admin/runners/index.html.haml
index bacaccfbffa117ef7bbfab002e6c24183ad2daf4..c407972cd0875dd3c4b329acd7c649216eccf0f5 100644
--- a/app/views/ci/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -1,6 +1,20 @@
 %p.lead
-  %span To register a new runner you should enter the following registration token. With this token the runner will request a unique runner token and use that for future communication.
-  %code #{GitlabCi::REGISTRATION_TOKEN}
+  %span
+    To register a new runner you should enter the following registration token.
+    With this token the runner will request a unique runner token and use that for future communication.
+    Registration token is
+    %code{ id: 'runners-token' } #{current_application_settings.runners_registration_token}
+
+.bs-callout.clearfix
+  .pull-left
+    %p
+      You can reset runners registration token by pressing a button below.
+    %p
+      = button_to reset_runners_token_admin_application_settings_path,
+        method: :put, class: 'btn btn-default',
+        data: { confirm: 'Are you sure you want to reset registration token?' } do
+        = icon('refresh')
+        Reset runners registration token
 
 .bs-callout
   %p
@@ -25,7 +39,7 @@
 
 .append-bottom-20.clearfix
   .pull-left
-    = form_tag ci_admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
+    = form_tag admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
       .form-group
         = search_field_tag :search, params[:search], class: 'form-control', placeholder: 'Runner description or token', spellcheck: false
       = submit_tag 'Search', class: 'btn'
@@ -49,5 +63,5 @@
         %th
 
     - @runners.each do |runner|
-      = render "ci/admin/runners/runner", runner: runner
+      = render "admin/runners/runner", runner: runner
 = paginate @runners
diff --git a/app/views/ci/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
similarity index 69%
rename from app/views/ci/admin/runners/show.html.haml
rename to app/views/admin/runners/show.html.haml
index 1498db46a80ac044fed4a4b2f8c5d9a06e890d25..8700b4820cd6b9d65f5eee6edb427dbf97e4f3f7 100644
--- a/app/views/ci/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -22,7 +22,7 @@
     %h4 This runner will process builds only from ASSIGNED projects
     %p You can't make this a shared runner.
 %hr
-= form_for @runner, url: ci_admin_runner_path(@runner), html: { class: 'form-horizontal' } do |f|
+= form_for @runner, url: admin_runner_path(@runner), html: { class: 'form-horizontal' } do |f|
   .form-group
     = label_tag :token, class: 'control-label' do
       Token
@@ -37,7 +37,7 @@
     = label_tag :tag_list, class: 'control-label' do
       Tags
     .col-sm-10
-      = f.text_field :tag_list, class: 'form-control'
+      = f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control'
       .help-block You can setup builds to only use runners with specific tags
   .form-actions
     = f.submit 'Save', class: 'btn btn-save'
@@ -53,29 +53,24 @@
             %th
         - @runner.runner_projects.each do |runner_project|
           - project = runner_project.project
-          - if project.gl_project
+          - if project
             %tr.alert-info
               %td
                 %strong
-                  = project.name
+                  = project.name_with_namespace
               %td
                 .pull-right
-                  = link_to 'Disable', [:ci, :admin, project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
+                  = link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
 
     %table.table
       %thead
         %tr
           %th Project
           %th
-            .pull-right
-              = link_to 'Assign to all', assign_all_ci_admin_runner_path(@runner),
-                class: 'btn btn-sm assign-all-runner',
-                title: 'Assign runner to all projects',
-                method: :put
 
       %tr
         %td
-          = form_tag ci_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
+          = form_tag admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
             .form-group
               = search_field_tag :search, params[:search], class: 'form-control', spellcheck: false
             = submit_tag 'Search', class: 'btn'
@@ -84,44 +79,44 @@
       - @projects.each do |project|
         %tr
           %td
-            = project.name
+            = project.name_with_namespace
           %td
             .pull-right
-              = form_for [:ci, :admin, project, project.runner_projects.new] do |f|
+              = form_for [:admin, project.namespace.becomes(Namespace), project, project.runner_projects.new] do |f|
                 = f.hidden_field :runner_id, value: @runner.id
                 = f.submit 'Enable', class: 'btn btn-xs'
     = paginate @projects
 
   .col-md-6
     %h4 Recent builds served by this runner
-    %table.builds.runner-builds
+    %table.table.builds.runner-builds
       %thead
         %tr
-          %th Build ID
+          %th Build
           %th Status
           %th Project
           %th Commit
           %th Finished at
 
       - @builds.each do |build|
-        - gl_project = build.gl_project
+        - project = build.project
         %tr.build
           %td.id
-            - if gl_project
-              = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
-                = build.id
+            - if project
+              = link_to namespace_project_build_path(project.namespace, project, build) do
+                %strong ##{build.id}
             - else
-              = build.id
+              %strong ##{build.id}
 
           %td.status
             = ci_status_with_icon(build.status)
 
           %td.status
-            - if gl_project
-              = gl_project.name_with_namespace
+            - if project
+              = project.name_with_namespace
 
           %td.build-link
-            - if gl_project
+            - if project
               = link_to ci_status_path(build.commit) do
                 %strong #{build.commit.short_sha}
 
diff --git a/app/views/ci/admin/runners/update.js.haml b/app/views/admin/runners/update.js.haml
similarity index 100%
rename from app/views/ci/admin/runners/update.js.haml
rename to app/views/admin/runners/update.js.haml
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index 8d1cab4137c4950c5c557482a7e3e899b658cac7..5e17b018163d05f47b86f658b3aa50b0aa7993e2 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -6,7 +6,7 @@
     %span.cred (Admin)
 
   .pull-right
-    - unless @user == current_user
+    - unless @user == current_user || @user.blocked?
       = link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-grouped btn-info"
     = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do
       %i.fa.fa-pencil-square-o
diff --git a/app/views/admin/users/_projects.html.haml b/app/views/admin/users/_projects.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a126a858ea85f0ce96e785dc2ebc0297a161d38f
--- /dev/null
+++ b/app/views/admin/users/_projects.html.haml
@@ -0,0 +1,13 @@
+- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present?
+  .panel.panel-default.contributed-projects
+    .panel-heading Projects contributed to
+    = render 'shared/projects/list',
+      projects: contributed_projects.sort_by(&:star_count).reverse,
+      projects_limit: 5, stars: true, avatar: false
+
+- if local_assigns.has_key?(:projects) && projects.present?
+  .panel.panel-default
+    .panel-heading Personal projects
+    = render 'shared/projects/list',
+      projects: projects.sort_by(&:star_count).reverse,
+      projects_limit: 10, stars: true, avatar: false
diff --git a/app/views/admin/users/edit.html.haml b/app/views/admin/users/edit.html.haml
index a8837d74dd97043adb8300c71d37f04190e892a8..3b6fd71500d7145660c8bffa866bab18a9821521 100644
--- a/app/views/admin/users/edit.html.haml
+++ b/app/views/admin/users/edit.html.haml
@@ -1,8 +1,5 @@
 - page_title "Edit", @user.name, "Users"
 %h3.page-title
   Edit user: #{@user.name}
-.back-link
-  = link_to admin_user_path(@user) do
-    &larr; Back to user page
 %hr
 = render 'form'
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
index 0d7a1a25a8043bef5bb20c2a06ce338c19c5ab46..b655b2a15f5b123dcec41050ee77926de734172b 100644
--- a/app/views/admin/users/projects.html.haml
+++ b/app/views/admin/users/projects.html.haml
@@ -14,7 +14,7 @@
 .row
   .col-md-6
     - if @personal_projects.present?
-      = render 'users/projects', projects: @personal_projects
+      = render 'admin/users/projects', projects: @personal_projects
     - else
       .nothing-here-block This user has no personal projects.
 
diff --git a/app/views/ci/admin/application_settings/_form.html.haml b/app/views/ci/admin/application_settings/_form.html.haml
deleted file mode 100644
index 634c9daa4778f5cff59dc7c953341b1cdb0b90e9..0000000000000000000000000000000000000000
--- a/app/views/ci/admin/application_settings/_form.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-= form_for @application_setting, url: ci_admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
-  - if @application_setting.errors.any?
-    #error_explanation
-      .alert.alert-danger
-        - @application_setting.errors.full_messages.each do |msg|
-          %p= msg
-
-  %fieldset
-    %legend Default Project Settings
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :all_broken_builds do
-            = f.check_box :all_broken_builds
-            Send emails only on broken builds
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          = f.label :add_pusher do
-            = f.check_box :add_pusher
-            Add pusher to recipients list
-
-  .form-actions
-    = f.submit 'Save', class: 'btn btn-primary'
diff --git a/app/views/ci/admin/application_settings/show.html.haml b/app/views/ci/admin/application_settings/show.html.haml
deleted file mode 100644
index 7ef0aa89ed6741a41b0ef6db185f254cabdd2d60..0000000000000000000000000000000000000000
--- a/app/views/ci/admin/application_settings/show.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-%h3.page-title Settings
-%hr
-= render 'form'
diff --git a/app/views/ci/admin/builds/_build.html.haml b/app/views/ci/admin/builds/_build.html.haml
deleted file mode 100644
index 2df5871321404bd3ed5ecb03418e6ef8e2326f7c..0000000000000000000000000000000000000000
--- a/app/views/ci/admin/builds/_build.html.haml
+++ /dev/null
@@ -1,34 +0,0 @@
-- gl_project = build.project.gl_project
-- if build.commit && build.project
-  %tr.build
-    %td.build-link
-      = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
-        %strong #{build.id}
-
-    %td.status
-      = ci_status_with_icon(build.status)
-
-    %td.commit-link
-      = link_to ci_status_path(build.commit) do
-        %strong #{build.commit.short_sha}
-
-    %td.runner
-      - if build.runner
-        = link_to build.runner.id, ci_admin_runner_path(build.runner)
-
-    %td.build-project
-      = truncate build.project.name, length: 30
-
-    %td.build-message
-      %span= truncate(build.commit.git_commit_message, length: 30)
-
-    %td.build-branch
-      %span= truncate(build.ref, length: 25)
-
-    %td.duration
-      - if build.duration
-        #{duration_in_words(build.finished_at, build.started_at)}
-
-    %td.timestamp
-      - if build.finished_at
-        %span #{time_ago_in_words build.finished_at} ago
diff --git a/app/views/ci/admin/builds/index.html.haml b/app/views/ci/admin/builds/index.html.haml
deleted file mode 100644
index d23119162cc655204e5485e949e70c826626060c..0000000000000000000000000000000000000000
--- a/app/views/ci/admin/builds/index.html.haml
+++ /dev/null
@@ -1,28 +0,0 @@
-%ul.nav.nav-tabs.append-bottom-20
-  %li{class: ("active" if @scope.nil?)}
-    = link_to 'All builds', ci_admin_builds_path
-  
-  %li{class: ("active" if @scope == "pending")}
-    = link_to "Pending", ci_admin_builds_path(scope: :pending)
-
-  %li{class: ("active" if @scope == "running")}
-    = link_to "Running", ci_admin_builds_path(scope: :running)
-
-
-%table.builds
-  %thead
-    %tr
-      %th Build
-      %th Status
-      %th Commit
-      %th Runner
-      %th Project
-      %th Message
-      %th Branch
-      %th Duration
-      %th Finished at
-
-  - @builds.each do |build|
-    = render "ci/admin/builds/build", build: build
-
-= paginate @builds
diff --git a/app/views/ci/admin/events/index.html.haml b/app/views/ci/admin/events/index.html.haml
deleted file mode 100644
index 5a5b4dc7c3571cd2cee561369e3cf0c7ae01b87c..0000000000000000000000000000000000000000
--- a/app/views/ci/admin/events/index.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-.table-holder
-  %table.table
-    %thead
-      %tr
-        %th User ID
-        %th Description
-        %th When
-    - @events.each do |event|
-      %tr
-        %td
-          = event.user_id
-        %td
-          = event.description
-        %td.light
-          = time_ago_in_words event.updated_at
-          ago
-
-= paginate @events
diff --git a/app/views/ci/admin/projects/_project.html.haml b/app/views/ci/admin/projects/_project.html.haml
deleted file mode 100644
index a342d6e1cf0afb126c1f63954e14c75edc48ab6b..0000000000000000000000000000000000000000
--- a/app/views/ci/admin/projects/_project.html.haml
+++ /dev/null
@@ -1,29 +0,0 @@
-- last_commit = project.commits.last
-%tr
-  %td
-    = project.id
-  %td
-    = link_to [:ci, project] do
-      %strong= project.name
-  %td
-    - if last_commit
-      = ci_status_with_icon(last_commit.status)
-      - if project.last_commit_date
-        &middot;
-        = time_ago_in_words project.last_commit_date
-        ago
-    - else
-      No builds yet
-  %td
-    - if project.public
-      %i.fa.fa-globe
-      Public
-    - else
-      %i.fa.fa-lock
-      Private
-  %td
-    = project.commits.count
-  %td
-    = link_to [:ci, :admin, project], method: :delete, class: 'btn btn-danger btn-sm' do
-      %i.fa.fa-remove
-      Remove
diff --git a/app/views/ci/admin/projects/index.html.haml b/app/views/ci/admin/projects/index.html.haml
deleted file mode 100644
index 0da8547924bb8c41244245eabbcc0288e14c681a..0000000000000000000000000000000000000000
--- a/app/views/ci/admin/projects/index.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-.table-holder
-  %table.table
-    %thead
-      %tr
-        %th ID
-        %th Name
-        %th Last build
-        %th Access
-        %th Builds
-        %th
-
-    - @projects.each do |project|
-      = render "ci/admin/projects/project", project: project
-
-= paginate @projects
-
diff --git a/app/views/ci/admin/runner_projects/index.html.haml b/app/views/ci/admin/runner_projects/index.html.haml
deleted file mode 100644
index 6b4e3b2cb386bf2dab3b200b9a5f6779191746ee..0000000000000000000000000000000000000000
--- a/app/views/ci/admin/runner_projects/index.html.haml
+++ /dev/null
@@ -1,57 +0,0 @@
-%p.lead
-  To register a new runner visit #{link_to 'this page ', ci_runners_path}
-
-.row
-  .col-md-8
-    %h5 Activated:
-    %table.table
-      %tr
-        %th Runner ID
-        %th Runner Description
-        %th Last build
-        %th Builds Stats
-        %th Registered
-        %th
-
-      - @runner_projects.each do |runner_project|
-        - runner = runner_project.runner
-        - builds = runner.builds.where(project_id: @project.id)
-        %tr
-          %td
-            %span.badge.badge-info= runner.id
-          %td
-            = runner.display_name
-          %td
-            - last_build = builds.last
-            - if last_build
-              = link_to last_build.short_sha, [last_build.project, last_build]
-            - else
-              unknown
-          %td
-            %span.badge.badge-success
-              #{builds.success.count}
-            %span /
-            %span.badge.badge-important
-              #{builds.failed.count}
-          %td
-            #{time_ago_in_words(runner_project.created_at)} ago
-          %td
-            = link_to 'Disable', [:ci, @project, runner_project], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm right'
-  .col-md-4
-    %h5 Available
-    %table.table
-      %tr
-        %th ID
-        %th Token
-        %th
-
-      - (Ci::Runner.all - @project.runners).each do |runner|
-        %tr
-          %td
-            = runner.id
-          %td
-            = runner.token
-          %td
-            = form_for [:ci, @project, @runner_project] do |f|
-              = f.hidden_field :runner_id, value: runner.id
-              = f.submit 'Add', class: 'btn btn-sm'
diff --git a/app/views/ci/commits/_commit.html.haml b/app/views/ci/commits/_commit.html.haml
index b24a3b826cfb52a0a8a36144d45ccf5d31111763..11163813f3e159bbeaf2cf1d515703938bd016bb 100644
--- a/app/views/ci/commits/_commit.html.haml
+++ b/app/views/ci/commits/_commit.html.haml
@@ -27,7 +27,6 @@
     - if commit.finished_at
       %span #{time_ago_in_words commit.finished_at} ago
 
-  - if commit.project.coverage_enabled?
+  - if commit.coverage
     %td.coverage
-      - if commit.coverage
-        #{commit.coverage}%
+      #{commit.coverage}%
diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml
index 77f78caa8d886ac22324b676429399862314e8e3..f7875e68b7e668745e10e238e7c7d1899b820a66 100644
--- a/app/views/ci/lints/_create.html.haml
+++ b/app/views/ci/lints/_create.html.haml
@@ -41,5 +41,3 @@
     %i.fa.fa-remove.incorrect-syntax
   %b Error:
   = @error
-
-    
diff --git a/app/views/ci/lints/create.js.haml b/app/views/ci/lints/create.js.haml
deleted file mode 100644
index a96c0b11b6e06cb89baed0c3730f8c028883f675..0000000000000000000000000000000000000000
--- a/app/views/ci/lints/create.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-:plain
-  $(".results").html("#{escape_javascript(render "create")}")
\ No newline at end of file
diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml
index fb9057e4882069229f557c83af711e1dbc719ab3..a144c43be477a260ccdad87422f114634285f135 100644
--- a/app/views/ci/lints/show.html.haml
+++ b/app/views/ci/lints/show.html.haml
@@ -1,27 +1,17 @@
 %h2 Check your .gitlab-ci.yml
 %hr
 
-= form_tag ci_lint_path, method: :post, remote: true do
-  .control-group
-    = label_tag :content, "Content of .gitlab-ci.yml", class: 'control-label'
-    .controls
-      = text_area_tag :content, nil, class: 'form-control span1', rows: 7, require: true
+.row
+  = form_tag ci_lint_path, method: :post do
+    .form-group
+      = label_tag :content, 'Content of .gitlab-ci.yml', class: 'control-label text-nowrap'
+      .col-sm-12
+        = text_area_tag :content, nil, class: 'form-control span1', rows: 7, require: true
+    .col-sm-12
+      .pull-left.prepend-top-10
+        = submit_tag 'Validate', class: 'btn btn-success submit-yml'
 
-  .control-group.clearfix
-    .controls.pull-left.prepend-top-10
-      = submit_tag "Validate", class: 'btn btn-success submit-yml'
-
-
-%p.text-center.loading
-  %i.fa.fa-refresh.fa-spin
-
-.results.prepend-top-20
-
-:javascript
-  $(".loading").hide();
-  $('form').bind('ajax:beforeSend', function() {
-    $(".loading").show();
-  });
-  $('form').bind('ajax:complete', function() {
-    $(".loading").hide();
-  });
+.row.prepend-top-20
+  .col-sm-12
+    .results
+      = render partial: 'create' if defined?(@status)
diff --git a/app/views/ci/shared/_guide.html.haml b/app/views/ci/shared/_guide.html.haml
index db2d7f2f4b650c675d67c964cd5bc9bbaf26bf83..09e7e6535217c0d3c4b1fa9837237e9875f2aee1 100644
--- a/app/views/ci/shared/_guide.html.haml
+++ b/app/views/ci/shared/_guide.html.haml
@@ -4,12 +4,10 @@
   %ol
     %li
       Add at least one runner to the project.
-      Go to #{link_to 'Runners page', runners_path(@project.gl_project), target: :blank} for instructions.
+      Go to #{link_to 'Runners page', runners_path(@project), target: :blank} for instructions.
     %li
-      Put the .gitlab-ci.yml in the root of your repository. Examples can be found in #{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}.
+      Put the .gitlab-ci.yml in the root of your repository. Examples can be found in
+      #{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}.
       You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
-    %li
-      Visit #{link_to 'GitLab project settings', @project.gitlab_url + "/services/gitlab_ci/edit", target: :blank}
-      and press the "Test settings" button.
     %li
       Return to this page and refresh it, it should show a new build.
diff --git a/app/views/ci/user_sessions/new.html.haml b/app/views/ci/user_sessions/new.html.haml
deleted file mode 100644
index b8d9a1d7089aca03e522be413f4c439510a87c58..0000000000000000000000000000000000000000
--- a/app/views/ci/user_sessions/new.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-.login-block
-  %h2 Login using GitLab account
-  %p.light
-    Make sure you have an account on the GitLab server
-    = link_to GitlabCi.config.gitlab_server.url, GitlabCi.config.gitlab_server.url, no_turbolink
-  %hr
-  = link_to "Login with GitLab", auth_ci_user_sessions_path(state: params[:state]), no_turbolink.merge( class: 'btn btn-login btn-success' )
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index ed480b8caf8abae4690ec4cc120938cd6c6fdb3f..f4a3e3162bf5da1eeaecc7d4b6b56f290a94f427 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -1,10 +1,20 @@
-%ul.center-top-menu
-  = nav_link(path: ['projects#index', 'root#index']) do
-    = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
-      Your Projects
-  = nav_link(page: starred_dashboard_projects_path) do
-    = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
-      Starred Projects
-  = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do
-    = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
-      Explore Projects
+= content_for :flash_message do
+  = render 'shared/project_limit'
+.top-area
+  %ul.left-top-menu
+    = nav_link(page: [dashboard_projects_path, root_path]) do
+      = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
+        Your Projects
+    = nav_link(page: starred_dashboard_projects_path) do
+      = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
+        Starred Projects
+    = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do
+      = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
+        Explore Projects
+
+  .projects-search-form  
+    = search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs', spellcheck: false
+    - if current_user.can_create_project?
+      = link_to new_project_path, class: 'btn btn-green' do
+        %i.fa.fa-plus
+        New Project
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index f3f3f58111e3cf8d54bdd4e3b90ed6d74e405679..d5b7e729e7bc026569061948d399779df257a23e 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -8,8 +8,8 @@
       = link_to new_group_path, class: "btn btn-new" do
         %i.fa.fa-plus
         New Group
-  .title Welcome to the groups!
-  Group members have access to all group projects.
+  .oneline
+    Group members have access to all group projects.
 
 %ul.content-list
   - @group_members.each do |group_member|
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index cd602e897b7f0809034389d3bf34ef4af9b32f94..2d3da01178a0a6adf4b353e85333e30f3b8858e1 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,14 +4,20 @@
   - if current_user
     = auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
 
+.project-issuable-filter
+  .controls
+    .pull-left
+      - if current_user
+        .hidden-xs.pull-left
+          = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
+            %i.fa.fa-rss
 
-.append-bottom-20
-  .pull-right
-    - if current_user
-      .hidden-xs.pull-left.prepend-top-20
-        = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: '' do
-          %i.fa.fa-rss
+    = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
 
   = render 'shared/issuable/filter', type: :issues
 
-= render 'shared/issues'
+.gray-content-block.second-block
+  List all issues from all projects you have access to.
+
+.prepend-top-default
+  = render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index d1f332fa0d3ad3fd4b32ec032ff199ab58595a1c..c5a5ec21f78aa35e3b0f2501cd88cfae96ac2e97 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -1,6 +1,14 @@
 - page_title "Merge Requests"
 - header_title  "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
 
-.append-bottom-20
+.project-issuable-filter
+  .controls
+    = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
+
   = render 'shared/issuable/filter', type: :merge_requests
-= render 'shared/merge_requests'
+
+.gray-content-block.second-block
+  List all merge requests from all projects you have access to.
+
+.prepend-top-default
+  = render 'shared/merge_requests'
diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml
index 55080d6b3fe5f85e773af02a5e78feb271da1145..7c882a327025698db068b0f4e1149a9bd1543d50 100644
--- a/app/views/dashboard/milestones/_milestone.html.haml
+++ b/app/views/dashboard/milestones/_milestone.html.haml
@@ -16,7 +16,10 @@
       = milestone_progress_bar(milestone)
   .row
     .col-sm-6
-      - milestone.milestones.each do |milestone|
-        = link_to milestone_path(milestone) do
-          %span.label.label-gray
-            = milestone.project.name_with_namespace
+      .expiration
+        = render 'shared/milestone_expired', milestone: milestone
+      .projects
+        - milestone.milestones.each do |milestone|
+          = link_to milestone_path(milestone) do
+            %span.label.label-gray
+              = milestone.project.name_with_namespace
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index 635251e2374e82ceed3bc9b4a31b80489a9fe4b8..bec1692a4dedda7647d8ed053a1c1d04914fb227 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -1,12 +1,14 @@
 - page_title "Milestones"
-- header_title  "Milestones", dashboard_milestones_path
+- header_title "Milestones", dashboard_milestones_path
 
+.project-issuable-filter
+  .controls
+    = render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
 
-= render 'shared/milestones_filter'
+  = render 'shared/milestones_filter'
 
 .gray-content-block
-  .oneline
-    List all milestones from all projects you have access to.
+  List all milestones from all projects you have access to.
 
 .milestones
   %ul.content-list
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
index 83077a398bd2fdefa855fc812de5c8c4bda2163f..4316c358dcb81c2aa0f87e490b95499d529b8156 100644
--- a/app/views/dashboard/milestones/show.html.haml
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -1,19 +1,23 @@
 - page_title @milestone.title, "Milestones"
-%h4.page-title
-  .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
+- header_title "Milestones", dashboard_milestones_path
+
+.detail-page-header
+  .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
     - if @milestone.closed?
       Closed
     - else
       Open
-  Milestone #{@milestone.title}
+  %span.identifier
+    Milestone #{@milestone.title}
+
+.detail-page-description.gray-content-block.second-block
+  %h2.title
+    = markdown escape_once(@milestone.title), pipeline: :single_line
 
-%hr
 - if @milestone.complete? && @milestone.active?
-  .alert.alert-success
+  .alert.alert-success.prepend-top-default
     %span All issues for this milestone are closed. You may close the milestone now.
 
-.description
-
 .table-holder
   %table.table
     %thead
@@ -44,7 +48,7 @@
     #{@milestone.open_items_count} open
   = milestone_progress_bar(@milestone)
 
-%ul.nav.nav-tabs
+%ul.center-top-menu.no-top.no-bottom
   %li.active
     = link_to '#tab-issues', 'data-toggle' => 'tab' do
       Issues
@@ -58,25 +62,39 @@
       Participants
       %span.badge= @milestone.participants.count
 
-  .pull-right
-    = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn  edit-milestone-link btn-grouped"
-
 .tab-content
   .tab-pane.active#tab-issues
-    .row
+    .gray-content-block.middle-block
+      .pull-right
+        = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
+
+      .oneline
+        All issues in this milestone
+
+    .row.prepend-top-default
       .col-md-6
         = render 'issues', title: "Open", issues: @milestone.opened_issues
       .col-md-6
         = render 'issues', title: "Closed", issues: @milestone.closed_issues
 
   .tab-pane#tab-merge-requests
-    .row
+    .gray-content-block.middle-block
+      .pull-right
+        = link_to 'Browse Merge Requests', merge_requests_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
+
+      .oneline
+        All merge requests in this milestone
+
+    .row.prepend-top-default
       .col-md-6
         = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
       .col-md-6
         = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
 
   .tab-pane#tab-participants
+    .gray-content-block.middle-block
+      .oneline
+        All participants to this milestone
     %ul.bordered-list
       - @milestone.participants.each do |user|
         %li
diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml
index 81a5909e2d2ac5f01430f000bf0512306320cedd..cea9ffcc748ad866e87d924b1785da19b94f10e4 100644
--- a/app/views/dashboard/projects/_projects.html.haml
+++ b/app/views/dashboard/projects/_projects.html.haml
@@ -1,11 +1,3 @@
 .projects-list-holder
-  .projects-search-form
-    .input-group
-      = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
-      - if current_user.can_create_project?
-        %span.input-group-btn
-          = link_to new_project_path, class: 'btn btn-green' do
-            %i.fa.fa-plus
-            New Project
 
   = render 'shared/projects/list', projects: @projects, ci: true
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 7a16b811f6bcf4fea0e981d6719b59a732b0d0c4..53abf274bdbf68ca7389de872b2046c0f153edfb 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -3,7 +3,7 @@
     = auto_discovery_link_tag(:atom, dashboard_projects_url(format: :atom, private_token: current_user.private_token), title: "All activity")
 
 - page_title    "Projects"
-- header_title  "Projects", root_path
+- header_title  "Projects", dashboard_projects_path
 
 = render 'dashboard/projects_head'
 
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index f75f2e0a32a61d232ae7f6142f740089582ec077..70705923d42b9cf64d3536abce06cff1c7372a31 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -1,5 +1,5 @@
 - page_title "Starred Projects"
-- header_title "Projects", projects_path
+- header_title "Projects", dashboard_projects_path
 
 = render 'dashboard/projects_head'
 
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb
deleted file mode 100644
index 79d6c761d8fce48f4bf9e757392c13cff3ecf16d..0000000000000000000000000000000000000000
--- a/app/views/devise/mailer/unlock_instructions.html.erb
+++ /dev/null
@@ -1,7 +0,0 @@
-<p>Hello <%= @resource.email %>!</p>
-
-<p>Your account has been locked due to an excessive amount of unsuccessful sign in attempts.</p>
-
-<p>Click the link below to unlock your account:</p>
-
-<p><%= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token) %></p>
diff --git a/app/views/devise/mailer/unlock_instructions.html.haml b/app/views/devise/mailer/unlock_instructions.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..52b327e20c5f71dec4a876fa2585ebfc921066c1
--- /dev/null
+++ b/app/views/devise/mailer/unlock_instructions.html.haml
@@ -0,0 +1,10 @@
+%p
+Hello #{@resource.name}!
+
+%p
+  Your GitLab account has been locked due to an excessive amount of unsuccessful
+  sign in attempts. Your account will automatically unlock in
+  = time_ago_in_words(Devise.unlock_in.from_now)
+  or you may click the link below to unlock now.
+
+%p= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token)
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 9dc6aeffd5947b903780be359101ab3ae8aa2cb5..cb93ff2465e055a77f1ee1405d02b129ed199f0b 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -6,17 +6,21 @@
     .login-heading
       %h3 Create an account
   .login-body
+    - user = params[:user].present? ? params[:user] : {}
     = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
       .devise-errors
         = devise_error_messages!
       %div
-        = f.text_field :name, class: "form-control top", placeholder: "Name", required: true
+        = f.text_field :name, class: "form-control top", value: user[:name], placeholder: "Name", required: true
       %div
-        = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
+        = f.text_field :username, class: "form-control middle", value: user[:username], placeholder: "Username", required: true
       %div
-        = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
+        = f.email_field :email, class: "form-control middle", value: user[:email], placeholder: "Email", required: true
       .form-group.append-bottom-20#password-strength
-        = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true
+        = f.password_field :password, class: "form-control bottom", value: user[:password], id: "user_password_sign_up", placeholder: "Password", required: true
+      %div
+      - if current_application_settings.recaptcha_enabled
+        = recaptcha_tags
       %div
         = f.submit "Sign up", class: "btn-create btn"
 
diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb
deleted file mode 100644
index f9277d1673fe35c0ab7a80339cb20c1159871823..0000000000000000000000000000000000000000
--- a/app/views/devise/unlocks/new.html.erb
+++ /dev/null
@@ -1,12 +0,0 @@
-<h2>Resend unlock instructions</h2>
-
-<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
-  <%= devise_error_messages! %>
-
-  <div><%= f.label :email %><br />
-  <%= f.email_field :email %></div>
-
-  <div><%= f.submit "Resend unlock instructions" %></div>
-<% end %>
-
-<%= render partial: "devise/shared/links" %>
diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..49c087c0646e42112a6266225eb0ecf0aa225039
--- /dev/null
+++ b/app/views/devise/unlocks/new.html.haml
@@ -0,0 +1,14 @@
+.login-box
+  .login-heading
+    %h3 Resend unlock email
+  .login-body
+    = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f|
+      .devise-errors
+        = devise_error_messages!
+      .clearfix.append-bottom-20
+        = f.email_field :email, class: 'form-control', placeholder: 'Email', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off'
+      .clearfix
+        = f.submit 'Resend unlock instructions', class: 'btn btn-success'
+
+.clearfix.prepend-top-20
+  = render 'devise/shared/sign_in_link'
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index ad63841ccf3fee2d9f7ec3870a7a3637935b2aff..4ba8b84fd923cd308852f4e084544f2bceb85dc4 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -2,4 +2,4 @@
   .commit-row-title
     = link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: ''
     &middot;
-    = gfm event_commit_title(commit[:message]), project: project
+    = markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index 2761272aa8ab15d40a2d575da4f2da92bbf391e6..28b12c8dca876a104d88a2a5b3578a2d49648880 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -3,7 +3,7 @@
     .form-group
       = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false
     .form-group
-      = button_tag 'Search', class: "btn btn-success"
+      = button_tag 'Search', class: "btn"
 
 .pull-right.hidden-sm.hidden-xs
   - if current_user
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index 67e38ca3127f1adf767ecc06cd3a446b84058c27..b9a958fbe7be505ab4a7aa705e91beef3dc518d4 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -1,12 +1,12 @@
 - page_title    "Projects"
-- header_title  "Projects", root_path
+- header_title  "Projects", dashboard_projects_path
 
 - if current_user
   = render 'dashboard/projects_head'
 - else
   = render 'explore/head'
 
-.gray-content-block.clearfix
+.gray-content-block.clearfix.second-block
   = render 'filter'
 = render 'projects', projects: @projects
 = paginate @projects, theme: "gitlab"
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index 596cb0a96cd8f72d7bb192cb26e106135202f55a..95d46e331f8a1d4dc41aeee78142645c349440cc 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -1,5 +1,5 @@
 - page_title    "Projects"
-- header_title  "Projects", root_path
+- header_title  "Projects", dashboard_projects_path
 
 - if current_user
   = render 'dashboard/projects_head'
@@ -7,7 +7,7 @@
   = render 'explore/head'
 
 .explore-trending-block
-  .gray-content-block
+  .gray-content-block.second-block
     .pull-right
       = render 'explore/projects/dropdown'
     .oneline
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index 5ea6d81c5b9b9c1b054cc2a0f0298339edacc87a..fa0b718e48b7b37c6fc02b2dd347b9875a5d8264 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -1,5 +1,5 @@
 - page_title    "Projects"
-- header_title  "Projects", root_path
+- header_title  "Projects", dashboard_projects_path
 
 - if current_user
   = render 'dashboard/projects_head'
@@ -7,7 +7,7 @@
   = render 'explore/head'
 
 .explore-trending-block
-  .gray-content-block
+  .gray-content-block.second-block
     .pull-right
       = render 'explore/projects/dropdown'
     .oneline
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 11d69977ef9e03b87fd2c5bb113cd131651fb1d7..bbafc08435af438ca1f99bd3948208d73138910f 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -1,5 +1,5 @@
-.panel.panel-default.projects-list-holder
-  .panel-heading.clearfix
+.projects-list-holder
+  .projects-search-form
     .input-group
       = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
       - if can? current_user, :create_projects, @group
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 57308a661c0827ddd86343514254f080c2fbeb8e..1dea77c2e96be9c8211f3157448d268d56e9d362 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -3,8 +3,7 @@
 
 .panel.panel-default
   .panel-heading
-    %strong= @group.name
-    group settings:
+    Group settings
   .panel-body
     = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
       - if @group.errors.any?
@@ -32,7 +31,7 @@
         .col-sm-10
           .checkbox
             = f.check_box :public
-            %span.descr Make this group public (even if there is no any public project inside this group)
+            %span.descr Make this group public (even if there are no public projects inside this group)
 
       .form-actions
         = f.submit 'Save group', class: "btn btn-save"
@@ -45,4 +44,5 @@
       %br
       %strong Removed group can not be restored!
 
-    = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
+    .form-actions
+      = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index be94b1abc11fdf2de51de541309f1bd224b66b82..a79a0fcdc8e3a5a1a5d8b015078bf9a3cf997583 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -4,7 +4,7 @@
 %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
   %span{class: ("list-item-name" if show_controls)}
     - if member.user
-      = image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
+      = image_tag avatar_icon(user, 24), class: "avatar s24", alt: ''
       %strong
         = link_to user.name, user_path(user)
       %span.cgray= user.username
@@ -14,7 +14,7 @@
         %label.label.label-danger
           %strong Blocked
     - else
-      = image_tag avatar_icon(member.invite_email, 16), class: "avatar s16", alt: ''
+      = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: ''
       %strong
         = member.invite_email
       %span.cgray
@@ -30,7 +30,7 @@
 
   - if should_user_see_group_roles?(current_user, @group)
     %span.pull-right
-      %strong= member.human_access
+      %strong.member-access-level= member.human_access
       - if show_controls
         - if can?(current_user, :update_group_member, member)
           = button_tag class: "btn-xs btn js-toggle-button",
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index d4ad33a8bf144a68373ed24747447ac5219a3f30..335bf036074367eb98b460408a7e231a2fb1fa2f 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,38 +1,35 @@
 - page_title "Members"
 - header_title group_title(@group, "Members", group_group_members_path(@group))
-- if should_user_see_group_roles?(current_user, @group)
-  %p.light
-    Members of group have access to all group projects.
-    Read more about permissions
-    %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
-
-
-.clearfix.js-toggle-container
-  = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form'  do
-    .form-group
-      = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false }
-    = button_tag 'Search', class: 'btn'
+- @blank_container = true
 
+.group-members-page
   - if current_user && current_user.can?(:admin_group_member, @group)
-    .pull-right
-      = button_tag class: 'btn btn-new js-toggle-button', type: 'button' do
-        Add members
-        %i.fa.fa-chevron-down
-
-    .js-toggle-content.hide.new-group-member-holder
-      = render "new_group_member"
-
-.panel.panel-default.prepend-top-20
-  .panel-heading
-    %strong #{@group.name}
-    group members
-    %small
-      (#{@members.total_count})
-  %ul.well-list
-    - @members.each do |member|
-      = render 'groups/group_members/group_member', member: member, show_controls: true
+    .panel.panel-default
+      .panel-heading
+        Add new user to group
+      .panel-body
+        - if should_user_see_group_roles?(current_user, @group)
+          %p.light
+            Members of group have access to all group projects.
+        .new-group-member-holder
+          = render "new_group_member"
 
-= paginate @members, theme: 'gitlab'
+  .panel.panel-default
+    .panel-heading
+      %strong #{@group.name}
+      group members
+      %small
+        (#{@members.total_count})
+      .pull-right
+        = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form'  do
+          .form-group
+            = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
+          = button_tag class: 'btn', title: 'Search' do
+            = icon("search")
+    %ul.content-list
+      - @members.each do |member|
+        = render 'groups/group_members/group_member', member: member, show_controls: true
+    = paginate @members, theme: 'gitlab'
 
 :javascript
   $('form.member-search-form').on('submit', function(event) {
diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml
index 5bad48abafd0e908d7dcf6eca0de33c39fefdfc2..df726e2b2b9f355528af9b3ba3247c159815847c 100644
--- a/app/views/groups/group_members/update.js.haml
+++ b/app/views/groups/group_members/update.js.haml
@@ -1,2 +1,2 @@
 :plain
-  $("##{dom_id(@member)}").replaceWith('#{escape_javascript(render(@member, member: @member, show_controls: true))}');
+  $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render(@group_member, member: @group_member, show_controls: true))}');
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 08d97e418a334083bb0ca55f9424103d035759e0..90ade1e1680b07b60b9063e75ab2afd17d9f8620 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -4,21 +4,24 @@
   - if current_user
     = auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
 
+.project-issuable-filter
+  .controls
+    .pull-left
+      - if current_user
+        .hidden-xs.pull-left
+          = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
+            %i.fa.fa-rss
 
+    = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
+
+  = render 'shared/issuable/filter', type: :issues
 
-= render 'shared/issuable/filter', type: :issues
 .gray-content-block.second-block
-  .pull-right
-    - if current_user
-      .hidden-xs.pull-left
-        = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token) do
-          %i.fa.fa-rss
-  %div
-    Only issues from
-    %strong #{@group.name}
-    group are listed here.
-    - if current_user
-      To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
+  Only issues from
+  %strong #{@group.name}
+  group are listed here.
+  - if current_user
+    To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
 
 .prepend-top-default
   = render 'shared/issues'
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 425ad8331bffb6228a5f4e8f2d8970e499aae712..f662f5a8c17e2fd72c9350297604321babe93fd4 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -1,13 +1,18 @@
 - page_title "Merge Requests"
 - header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
 
-= render 'shared/issuable/filter', type: :merge_requests
+.project-issuable-filter
+  .controls
+    = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
+
+  = render 'shared/issuable/filter', type: :merge_requests
+
 .gray-content-block.second-block
-  %div
-    Only merge requests from
-    %strong #{@group.name}
-    group are listed here.
-    - if current_user
-      To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
+  Only merge requests from
+  %strong #{@group.name}
+  group are listed here.
+  - if current_user
+    To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
+
 .prepend-top-default
   = render 'shared/merge_requests'
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 84ec77c61888e082a057a9e6e74230bf8520e416..b221d3a89a4a5654b46f6ad50b4555da30f2a1e7 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,18 +1,22 @@
 - page_title "Milestones"
 - header_title group_title(@group, "Milestones", group_milestones_path(@group))
 
-= render 'shared/milestones_filter'
+.project-issuable-filter
+  .controls
+    - if can?(current_user, :admin_milestones, @group)
+      .pull-right
+        %span.pull-right.hidden-xs
+          = link_to new_group_milestone_path(@group), class: "btn btn-new" do
+            = icon('plus')
+            New Milestone
+
+  = render 'shared/milestones_filter'
+
 .gray-content-block
-  - if can?(current_user, :admin_milestones, @group)
-    .pull-right
-      %span.pull-right.hidden-xs
-        = link_to new_group_milestone_path(@group), class: "btn btn-new" do
-          New Milestone
+  Only milestones from
+  %strong #{@group.name}
+  group are listed here.
 
-  .oneline
-    Only milestones from
-    %strong #{@group.name}
-    group are listed here.
 .milestones
   %ul.content-list
     - if @milestones.blank?
diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml
index 800bac4ef023478e9d4e84e7a5e1c077738c51f2..3894a0ece7425f6b9616ed5df0c5a3068efabb8a 100644
--- a/app/views/groups/milestones/new.html.haml
+++ b/app/views/groups/milestones/new.html.haml
@@ -14,8 +14,7 @@
       .form-group
         = f.label :title, "Title", class: "control-label"
         .col-sm-10
-          = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true
-          %p.hint Required
+          = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true
       .form-group.milestone-description
         = f.label :description, "Description", class: "control-label"
         .col-sm-10
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index d161259e4aa0d9714367116642d78d60df5699a8..d063b257b5e6df7aae5f9d79fdbdfc165718f228 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,27 +1,29 @@
 - page_title @milestone.title, "Milestones"
 = render "header_title"
 
-%h4.page-title
-  .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
+.detail-page-header
+  .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
     - if @milestone.closed?
       Closed
     - else
       Open
-  Milestone #{@milestone.title}
+  %span.identifier
+    Milestone #{@milestone.title}
   .pull-right
     - if can?(current_user, :admin_milestones, @group)
       - if @milestone.active?
-        = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
+        = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
       - else
-        = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
+        = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
+
+.detail-page-description.gray-content-block.second-block
+  %h2.title
+    = markdown escape_once(@milestone.title), pipeline: :single_line
 
-%hr
 - if @milestone.complete? && @milestone.active?
-  .alert.alert-success
+  .alert.alert-success.prepend-top-default
     %span All issues for this milestone are closed. You may close the milestone now.
 
-.description
-
 .table-holder
   %table.table
     %thead
@@ -52,7 +54,7 @@
     #{@milestone.open_items_count} open
   = milestone_progress_bar(@milestone)
 
-%ul.nav.nav-tabs
+%ul.center-top-menu.no-top.no-bottom
   %li.active
     = link_to '#tab-issues', 'data-toggle' => 'tab' do
       Issues
@@ -66,25 +68,40 @@
       Participants
       %span.badge= @milestone.participants.count
 
-  .pull-right
-    = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn  edit-milestone-link btn-grouped"
-
 .tab-content
   .tab-pane.active#tab-issues
-    .row
+    .gray-content-block.middle-block
+      .pull-right
+        = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
+
+      .oneline
+        All issues in this milestone
+
+    .row.prepend-top-default
       .col-md-6
         = render 'issues', title: "Open", issues: @milestone.opened_issues
       .col-md-6
         = render 'issues', title: "Closed", issues: @milestone.closed_issues
 
   .tab-pane#tab-merge-requests
-    .row
+    .gray-content-block.middle-block
+      .pull-right
+        = link_to 'Browse Merge Requests', merge_requests_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
+
+      .oneline
+        All merge requests in this milestone
+
+    .row.prepend-top-default
       .col-md-6
         = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
       .col-md-6
         = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
 
   .tab-pane#tab-participants
+    .gray-content-block.middle-block
+      .oneline
+        All participants to this milestone
+
     %ul.bordered-list
       - @milestone.participants.each do |user|
         %li
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 0665cdf387afb7ea3dc7e00dbe596b69e0794a2b..4bc31cabea61def06d9e0e50420ba3dce7c23548 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -1,5 +1,10 @@
 - page_title    'New Group'
-- header_title  'New Group'
+- header_title  "Groups", dashboard_groups_path
+
+%h3.page-title
+  New Group
+%hr
+
 = form_for @group, html: { class: 'group-form form-horizontal' } do |f|
   - if @group.errors.any?
     .alert.alert-danger
@@ -18,3 +23,4 @@
 
   .form-actions
     = f.submit 'Create group', class: "btn btn-create", tabindex: 3
+    = link_to 'Cancel', dashboard_groups_path, class: 'btn btn-cancel'
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index dc8e81323a63daf43f7e527162142042b3a37111..c2c7c581b3eff57c5ad3d522f86bc77544a72709 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -5,37 +5,47 @@
   - if current_user
     = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
 
-.dashboard
-  .header-with-avatar.clearfix
-    = image_tag group_icon(@group), class: "avatar group-avatar s90"
-    %h3
-      = @group.name
-    .username
-      @#{@group.path}
-    - if @group.description.present?
-      .description
-        = markdown(@group.description, pipeline: :description)
-  %hr
-
-  = render 'shared/show_aside'
-
-  - if can?(current_user, :read_group, @group)
-    .row
-      %section.activities.col-md-7
-        .hidden-xs
-          - if current_user
-            = render "events/event_last_push", event: @last_push
-            .pull-right
-              = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' do
-                %i.fa.fa-rss
-
-            = render 'shared/event_filter'
-            %hr
-
-        .content_list
-        = spinner
-      %aside.side.col-md-5
-        = render "projects", projects: @projects
-  - else
-    %p
-      This group does not have public projects
+.cover-block
+  .avatar-holder
+    = link_to group_icon(@group), target: '_blank' do
+      = image_tag group_icon(@group), class: "avatar group-avatar s90"
+  .cover-title
+    = @group.name
+
+  .cover-desc.username
+    @#{@group.path}
+
+  - if @group.description.present?
+    .cover-desc.description
+      = markdown(@group.description, pipeline: :description)
+
+- if can?(current_user, :read_group, @group)
+  %ul.center-top-menu.no-top
+    %li.active
+      = link_to "#activity", 'data-toggle' => 'tab' do
+        Activity
+    - if @projects.present?
+      %li
+        = link_to "#projects", 'data-toggle' => 'tab' do
+          Projects
+
+  .tab-content
+    .tab-pane.active#activity
+      .gray-content-block.activity-filter-block
+        - if current_user
+          = render "events/event_last_push", event: @last_push
+          .pull-right
+            = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' do
+              %i.fa.fa-rss
+
+          = render 'shared/event_filter'
+
+      .content_list
+      = spinner
+
+    .tab-pane#projects
+      = render "projects", projects: @projects
+
+- else
+  %p
+    This group does not have public projects
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 7e801b5332d9cc848a98e92f51c8e84db0bed059..e8e331dd10902ca7af6cbba70b36b2eab26aa642 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -219,11 +219,3 @@
                 %td.shortcut
                   .key r
                 %td Reply (quoting selected text)
-
-
-:javascript
-  $('.js-more-help-button').click(function (e) {
-    $(this).remove()l
-    $('.hidden-shortcut').show();
-    e.preventDefault();
-  });
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 2169a821fb24c6fc22c7c6a01224430d82726610..d9ffda884c8c56663721fa1f568365b7d293829e 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -31,11 +31,9 @@
 
   %h2#blocks Blocks
 
-  %h3
+  %h4
     %code .gray-content-block
 
-
-
   .gray-content-block.middle-block
     %h4 Normal block inside content
     = lorem
@@ -45,9 +43,28 @@
     = lorem
 
 
+  %h4
+    %code .cover-block
+  %br
+  .cover-block
+    .avatar-holder
+      = image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: ''
+    .cover-title
+      John Smith
+
+    .cover-desc
+      = lorem
+
+    .cover-controls
+      = link_to '#', class: 'btn btn-gray' do
+        = icon('pencil')
+      &nbsp;
+      = link_to '#', class: 'btn btn-gray' do
+        = icon('rss')
+
   %h2#lists Lists
 
-  %h3
+  %h4
     %code .content-list
   %ul.content-list
     %li
@@ -57,7 +74,7 @@
     %li
       One item
 
-  %h3
+  %h4
     %code .well-list
   %ul.well-list
     %li
@@ -67,7 +84,7 @@
     %li
       One item
 
-  %h3
+  %h4
     %code .panel .well-list
 
   .panel.panel-default
@@ -80,7 +97,7 @@
       %li
         One item
 
-  %h3
+  %h4
     %code .bordered-list
   %ul.bordered-list
     %li
@@ -121,7 +138,7 @@
 
   %h2#navs Navigation
 
-  %h3
+  %h4
     %code .center-top-menu
   .example
     %ul.center-top-menu
@@ -130,7 +147,7 @@
       %li
         %a Closed
 
-  %h3
+  %h4
     %code .btn-group.btn-group-next
   .example
     %div.btn-group.btn-group-next
@@ -138,7 +155,7 @@
       %a.btn Closed
 
 
-  %h3
+  %h4
     %code .nav.nav-tabs
   .example
     %ul.nav.nav-tabs
@@ -204,7 +221,7 @@
 
   %h2#forms Forms
 
-  %h3
+  %h4
     %code form.horizontal-form
 
   %form.form-horizontal
@@ -226,7 +243,7 @@
       .col-sm-offset-2.col-sm-10
         %button.btn.btn-default{:type => "submit"} Sign in
 
-  %h3
+  %h4
     %code form
 
   %form
@@ -243,7 +260,7 @@
     %button.btn.btn-default{:type => "submit"} Sign in
 
   %h2#file File
-  %h3
+  %h4
     %code .file-holder
 
   - blob = Snippet.new(content: "Wow\nSuch\nFile")
@@ -254,13 +271,12 @@
         .file-actions
           .btn-group
             %a.btn Edit
-            %a.btn Remove
+            %a.btn.btn-danger Remove
       .file-contenta.code
         = render 'shared/file_highlight', blob: blob
 
-
   %h2#markdown Markdown
-  %h3
+  %h4
     %code .md or .wiki and others
 
   Markdown rendering has a bit different css and presented in next UI elements:
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index 1f09a27e2d6d280b7bf7cc9d5eb1bb76b0f8bb59..aec2e836c9fdbac543af0252b8628d77f6eadd6c 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -1,4 +1,5 @@
 - page_title "Bitbucket import"
+- header_title "Projects", root_path
 %h3.page-title
   %i.fa.fa-bitbucket
   Import projects from Bitbucket
diff --git a/app/views/import/fogbugz/new.html.haml b/app/views/import/fogbugz/new.html.haml
index e1bb88ca4ed4bb6f80ce233b1bf8a0d19c85dcfb..5515fad6f48c98a2171496b8fd06f484c9d867b4 100644
--- a/app/views/import/fogbugz/new.html.haml
+++ b/app/views/import/fogbugz/new.html.haml
@@ -1,4 +1,5 @@
 - page_title "FogBugz Import"
+- header_title "Projects", root_path
 %h3.page-title
   %i.fa.fa-bug
   Import projects from FogBugz
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
index bc3c90294e3bcb0afc4e6180bad67a20210843ee..07338736bacd48449a7ea68a8321a9429fff869c 100644
--- a/app/views/import/fogbugz/new_user_map.html.haml
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -1,4 +1,5 @@
 - page_title 'User map', 'FogBugz import'
+- header_title "Projects", root_path
 %h3.page-title
   %i.fa.fa-bug
   Import projects from FogBugz
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index b902006597bd64239ba9fffa917dd044bbce21e8..6ee16c8be4b95386deb10702e3650de7a341312b 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -1,4 +1,5 @@
 - page_title "FogBugz import"
+- header_title "Projects", root_path
 %h3.page-title
   %i.fa.fa-bug
   Import projects from FogBugz
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index 0699321c8c00ac1cf624af837b78d9631cb96ba8..1416ee5bd5a0d7e00bbd6e9ba6ffd111f77c99b9 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -1,4 +1,5 @@
 - page_title "GitHub import"
+- header_title "Projects", root_path
 %h3.page-title
   %i.fa.fa-github
   Import projects from GitHub
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index f4a2b33af21fa9d851f392c9bac0ba364072a85f..911a55eb85dfc20eeb98ff2477796f610af5e051 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -1,4 +1,5 @@
 - page_title "GitLab.com import"
+- header_title "Projects", root_path
 %h3.page-title
   %i.fa.fa-heart
   Import projects from GitLab.com
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
index 71752d21efab67c3722cd75274826d51574d8526..6b0fa1edf8ce2d5a6e20b859b6d594de0208005c 100644
--- a/app/views/import/gitorious/status.html.haml
+++ b/app/views/import/gitorious/status.html.haml
@@ -1,4 +1,5 @@
 - page_title "Gitorious import"
+- header_title "Projects", root_path
 %h3.page-title
   %i.icon-gitorious.icon-gitorious-big
   Import projects from Gitorious.org
diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml
index 9c64e0a009fb73931f3845306911929d33b5ef6e..5d2f149cd5f013810b83045674f5cffc4caab95b 100644
--- a/app/views/import/google_code/new.html.haml
+++ b/app/views/import/google_code/new.html.haml
@@ -1,4 +1,5 @@
 - page_title "Google Code import"
+- header_title "Projects", root_path
 %h3.page-title
   %i.fa.fa-google
   Import projects from Google Code
@@ -6,7 +7,7 @@
 
 = form_tag callback_import_google_code_path, class: 'form-horizontal', multipart: true do
   %p
-    Follow the steps below to export your Google Code project data. 
+    Follow the steps below to export your Google Code project data.
     In the next step, you'll be able to select the projects you want to import.
   %ol
     %li
diff --git a/app/views/import/google_code/new_user_map.html.haml b/app/views/import/google_code/new_user_map.html.haml
index e53ebda7dc19683ab7d601aa73a4735d85504a20..0738b3db1ebcc5d1ab3cc0da8080a3b6814d1488 100644
--- a/app/views/import/google_code/new_user_map.html.haml
+++ b/app/views/import/google_code/new_user_map.html.haml
@@ -1,4 +1,5 @@
 - page_title "User map", "Google Code import"
+- header_title "Projects", root_path
 %h3.page-title
   %i.fa.fa-google
   Import projects from Google Code
@@ -8,31 +9,31 @@
   %p
     Customize how Google Code email addresses and usernames are imported into GitLab.
     In the next step, you'll be able to select the projects you want to import.
-  %p 
+  %p
     The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side.
   %ul
     %li
       %strong Default: Directly import the Google Code email address or username
       %p
-        <code>"johnsmith@example.com": "johnsm...@example.com"</code> 
-        will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com. 
+        <code>"johnsmith@example.com": "johnsm...@example.com"</code>
+        will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com.
         The email address or username is masked to ensure the user's privacy.
     %li
       %strong Map a Google Code user to a GitLab user
       %p
-        <code>"johnsmith@example.com": "@johnsmith"</code> 
-        will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com, 
+        <code>"johnsmith@example.com": "@johnsmith"</code>
+        will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com,
         and will set <a href="#">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com.
     %li
       %strong Map a Google Code user to a full name
       %p
-        <code>"johnsmith@example.com": "John Smith"</code> 
+        <code>"johnsmith@example.com": "John Smith"</code>
         will add "By John Smith" to all issues and comments originally created by johnsmith@example.com.
     %li
       %strong Map a Google Code user to a full email address
       %p
-        <code>"johnsmith@example.com": "johnsmith@example.com"</code> 
-        will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com. 
+        <code>"johnsmith@example.com": "johnsmith@example.com"</code>
+        will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com.
         By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address.
 
   .form-group
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index 8c64fd27e604373ce0d2d5b12dd7f20380c8e7a4..175ef6921cd19a656c652467ebb9c889127c1421 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -1,4 +1,5 @@
 - page_title "Google Code import"
+- header_title "Projects", root_path
 %h3.page-title
   %i.fa.fa-google
   Import projects from Google Code
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 74174a72f5a8c39d972e339c02d61f528063e8bf..2e0bd2007a3bf16b49c19741ebd638b570568466 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -1,10 +1,26 @@
-- page_title "GitLab"
-%head
+%head{prefix: "og: http://ogp.me/ns#"}
   %meta{charset: "utf-8"}
   %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'}
-  %meta{content: "GitLab Community Edition", name: "description"}
   %meta{name: 'referrer', content: 'origin-when-cross-origin'}
 
+  %meta{name: "description", content: page_description}
+
+  -# Open Graph - http://ogp.me/
+  %meta{property: 'og:type',        content: "object"}
+  %meta{property: 'og:site_name',   content: "GitLab"}
+  %meta{property: 'og:title',       content: page_title}
+  %meta{property: 'og:description', content: page_description}
+  %meta{property: 'og:image',       content: page_image}
+  %meta{property: 'og:url',         content: request.base_url + request.fullpath}
+
+  -# Twitter Card - https://dev.twitter.com/cards/types/summary
+  %meta{property: 'twitter:card',         content: "summary"}
+  %meta{property: 'twitter:title',        content: page_title}
+  %meta{property: 'twitter:description',  content: page_description}
+  %meta{property: 'twitter:image',        content: page_image}
+  = page_card_meta_tags
+
+  - page_title "GitLab"
   %title= page_title
 
   = favicon_link_tag 'favicon.ico'
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 352b8040cf4aa871649584aed4d018a273d8461b..ec7cd79bc542c60026c3265a95146d9715838717 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,8 +1,8 @@
-.page-with-sidebar{ class: nav_sidebar_class }
+.page-with-sidebar{ class: page_sidebar_class }
   = render "layouts/broadcast"
-  .sidebar-wrapper.nicescroll
+  .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
     .header-logo
-      = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
+      = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
         = brand_header_logo
         .gitlab-text-container
           %h3 GitLab
@@ -17,8 +17,8 @@
     .collapse-nav
       = render partial: 'layouts/collapse_button'
     - if current_user
-      = link_to current_user, class: 'sidebar-user' do
-        = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
+      = link_to current_user, class: 'sidebar-user', title: "Profile" do
+        = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
         .username
           = current_user.username
   .content-wrapper
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 1c738719bd84b3782c7e0cd60aa88a4c2cea6cbf..6591c52bdbde6c3e9bc01f02ad5fee5374f3361a 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -1,5 +1,5 @@
-- page_title    "Admin area"
-- header_title  "Admin area", admin_root_path
+- page_title    "Admin Area"
+- header_title  "Admin Area", admin_root_path
 - sidebar       "admin"
 
 = render template: "layouts/application"
diff --git a/app/views/layouts/ci/_nav_admin.html.haml b/app/views/layouts/ci/_nav_admin.html.haml
deleted file mode 100644
index dcda04a4638f49e46382e5f0445afdcdfa05bed8..0000000000000000000000000000000000000000
--- a/app/views/layouts/ci/_nav_admin.html.haml
+++ /dev/null
@@ -1,35 +0,0 @@
-%ul.nav.nav-sidebar
-  = nav_link do
-    = link_to admin_root_path, title: 'Back to admin', data: {placement: 'right'}, class: 'back-link' do
-      = icon('caret-square-o-left fw')
-      %span
-        Back to admin
-
-  %li.separate-item
-  = nav_link path: 'projects#index' do
-    = link_to ci_admin_projects_path do
-      = icon('list-alt fw')
-      %span
-        Projects
-  = nav_link path: 'events#index' do
-    = link_to ci_admin_events_path do
-      = icon('book fw')
-      %span
-        Events
-  = nav_link path: ['runners#index', 'runners#show'] do
-    = link_to ci_admin_runners_path do
-      = icon('cog fw')
-      %span
-        Runners
-        %span.count= Ci::Runner.count(:all)
-  = nav_link path: 'builds#index' do
-    = link_to ci_admin_builds_path do
-      = icon('link fw')
-      %span
-        Builds
-        %span.count= Ci::Build.count(:all)
-  = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
-    = link_to ci_admin_application_settings_path do
-      = icon('cogs fw')
-      %span
-        Settings
diff --git a/app/views/layouts/ci/_nav_project.html.haml b/app/views/layouts/ci/_nav_project.html.haml
deleted file mode 100644
index f094edbfa87cac6accd2c5e2380a45225f5176fe..0000000000000000000000000000000000000000
--- a/app/views/layouts/ci/_nav_project.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-%ul.nav.nav-sidebar
-  = nav_link do
-    = link_to project_path(@project.gl_project), title: 'Back to project', data: {placement: 'right'}, class: 'back-link' do
-      = icon('caret-square-o-left fw')
-      %span
-        Back to project
-  %li.separate-item
-    = nav_link path: 'events#index' do
-      = link_to ci_project_events_path(@project) do
-        = icon('book fw')
-        %span
-          Events
diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml
index ab3e29c3f424c63f106f62c28ddf0fb88cb75a3d..7e90af21b21708f5ec9050521441106aea9298ed 100644
--- a/app/views/layouts/ci/_page.html.haml
+++ b/app/views/layouts/ci/_page.html.haml
@@ -1,8 +1,8 @@
-.page-with-sidebar{ class: nav_sidebar_class }
+.page-with-sidebar{ class: page_sidebar_class }
   = render "layouts/broadcast"
-  .sidebar-wrapper.nicescroll
+  .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
     .header-logo
-      = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
+      = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
         = brand_header_logo
         .gitlab-text-container
           %h3 GitLab
@@ -14,8 +14,8 @@
     .collapse-nav
       = render partial: 'layouts/collapse_button'
     - if current_user
-      = link_to current_user, class: 'sidebar-user' do
-        = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
+      = link_to current_user, class: 'sidebar-user', title: "Profile" do
+        = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
         .username
           = current_user.username
   .content-wrapper
diff --git a/app/views/layouts/ci/admin.html.haml b/app/views/layouts/ci/admin.html.haml
deleted file mode 100644
index c8cb185d28cff4386ce30bf55e5ac891a6c8f25b..0000000000000000000000000000000000000000
--- a/app/views/layouts/ci/admin.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-!!! 5
-%html{ lang: "en"}
-  = render 'layouts/head'
-  %body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page}
-    - header_title = "Admin area"
-    - if current_user
-      = render "layouts/header/default", title: header_title
-    - else
-      = render "layouts/header/public", title: header_title
-
-    = render 'layouts/ci/page', sidebar: 'nav_admin'
diff --git a/app/views/layouts/ci/application.html.haml b/app/views/layouts/ci/application.html.haml
deleted file mode 100644
index 38023468d0beb1fb7290ddb17eb1d050c0fdfef8..0000000000000000000000000000000000000000
--- a/app/views/layouts/ci/application.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-!!! 5
-%html{ lang: "en"}
-  = render 'layouts/head'
-  %body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page}
-    - header_title = "Continuous Integration"
-    - if current_user
-      = render "layouts/header/default", title: header_title
-    - else
-      = render "layouts/header/public", title: header_title
-
-    = render 'layouts/ci/page'
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 95e077c339f36b707a66d7ad0159cb67363586ac..f08cb0a5428c20b00e093095c8c723c645adac66 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,13 +1,13 @@
 !!! 5
 %html{ lang: "en"}
   = render "layouts/head"
-  %body.ui_charcoal.login-page.application
+  %body.ui_charcoal.login-page.application.navless
     = render "layouts/header/empty"
     = render "layouts/broadcast"
     .container.navless-container
       .content
         = render "layouts/flash"
-        .row.prepend-top-20
+        .row
           .col-sm-5.pull-right
             = yield
           .col-sm-7.brand-holder.pull-left
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index 2af265a2296d6a7a547e250bf8de9f2f9d3d6712..915acc4612e1a51a5f0cc883a8fce869b21ce293 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -1,7 +1,7 @@
 !!! 5
 %html{ lang: "en"}
   = render "layouts/head"
-  %body{class: "#{user_application_theme} application"}
+  %body{class: "#{user_application_theme} application navless"}
     = render "layouts/header/empty"
     .container.navless-container
       = render "layouts/flash"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 3ca30d3baabd89114d18b52495d25f56678c08f5..3892ef8eefa026bb8f2332a0ff99f074d620db85 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -11,27 +11,27 @@
             %li.hidden-sm.hidden-xs
               = render 'layouts/search'
           %li.visible-sm.visible-xs
-            = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
+            = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
               = icon('search')
           - if session[:impersonator_id]
             %li.impersonation
-              = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop impersonation', data: { toggle: 'tooltip', placement: 'bottom' } do
+              = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
                 = icon('user-secret fw')
           - if current_user.is_admin?
             %li
-              = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
+              = link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
                 = icon('wrench fw')
           - if current_user.can_create_project?
             %li
-              = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
+              = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
                 = icon('plus fw')
           - if Gitlab::Sherlock.enabled?
             %li
               = link_to sherlock_transactions_path, title: 'Sherlock Transactions',
-                data: {toggle: 'tooltip', placement: 'bottom'} do
+                data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
                 = icon('tachometer fw')
           %li
-            = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do
+            = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
               = icon('sign-out')
 
       %h1.title= title
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 2079feeeab6d73c2c26a500ee8d2028b42a4519d..c60ac5eefac75770679858783f57e83dce1ff6c4 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -5,78 +5,85 @@
       %span
         Overview
   = nav_link(controller: [:admin, :projects]) do
-    = link_to admin_namespaces_projects_path, title: 'Projects', data: {placement: 'right'} do
+    = link_to admin_namespaces_projects_path, title: 'Projects' do
       = icon('cube fw')
       %span
         Projects
   = nav_link(controller: :users) do
-    = link_to admin_users_path, title: 'Users', data: {placement: 'right'} do
+    = link_to admin_users_path, title: 'Users' do
       = icon('user fw')
       %span
         Users
   = nav_link(controller: :groups) do
-    = link_to admin_groups_path, title: 'Groups', data: {placement: 'right'} do
+    = link_to admin_groups_path, title: 'Groups' do
       = icon('group fw')
       %span
         Groups
   = nav_link(controller: :deploy_keys) do
-    = link_to admin_deploy_keys_path, title: 'Deploy Keys', data: {placement: 'right'} do
+    = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
       = icon('key fw')
       %span
         Deploy Keys
-  = nav_link do
-    = link_to ci_admin_projects_path, title: 'Continuous Integration', data: {placement: 'right'} do
-      = icon('building fw')
+  = nav_link path: ['runners#index', 'runners#show'] do
+    = link_to admin_runners_path do
+      = icon('cog fw')
+      %span
+        Runners
+        %span.count= Ci::Runner.count(:all)
+  = nav_link path: 'builds#index' do
+    = link_to admin_builds_path do
+      = icon('link fw')
       %span
-        Continuous Integration
+        Builds
+        %span.count= Ci::Build.count(:all)
   = nav_link(controller: :logs) do
-    = link_to admin_logs_path, title: 'Logs', data: {placement: 'right'} do
+    = link_to admin_logs_path, title: 'Logs' do
       = icon('file-text fw')
       %span
         Logs
   = nav_link(controller: :broadcast_messages) do
-    = link_to admin_broadcast_messages_path, title: 'Broadcast Messages', data: {placement: 'right'} do
+    = link_to admin_broadcast_messages_path, title: 'Messages' do
       = icon('bullhorn fw')
       %span
         Messages
   = nav_link(controller: :hooks) do
-    = link_to admin_hooks_path, title: 'Hooks', data: {placement: 'right'} do
+    = link_to admin_hooks_path, title: 'Hooks' do
       = icon('external-link fw')
       %span
         Hooks
   = nav_link(controller: :background_jobs) do
-    = link_to admin_background_jobs_path, title: 'Background Jobs', data: {placement: 'right'} do
+    = link_to admin_background_jobs_path, title: 'Background Jobs' do
       = icon('cog fw')
       %span
         Background Jobs
 
   = nav_link(controller: :applications) do
-    = link_to admin_applications_path, title: 'Applications', data: {placement: 'right'} do
+    = link_to admin_applications_path, title: 'Applications' do
       = icon('cloud fw')
       %span
         Applications
 
   = nav_link(controller: :services) do
-    = link_to admin_application_settings_services_path, title: 'Service Templates', data: {placement: 'right'} do
+    = link_to admin_application_settings_services_path, title: 'Service Templates' do
       = icon('copy fw')
       %span
         Service Templates
 
   = nav_link(controller: :labels) do
-    = link_to admin_labels_path, title: 'Labels', data: {placement: 'right'} do
+    = link_to admin_labels_path, title: 'Labels' do
       = icon('tags fw')
       %span
         Labels
 
   = nav_link(controller: :abuse_reports) do
-    = link_to admin_abuse_reports_path, title: "Abuse reports" do
+    = link_to admin_abuse_reports_path, title: "Abuse Reports" do
       = icon('exclamation-circle fw')
       %span
         Abuse Reports
         %span.count= AbuseReport.count(:all)
 
   = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
-    = link_to admin_application_settings_path, title: 'Settings', data: {placement: 'right'} do
+    = link_to admin_application_settings_path, title: 'Settings' do
       = icon('cogs fw')
       %span
         Settings
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index b1a1d53184678bcb83ca59473b1169ca11e032ec..da6988313008521184012950cf3fc694ce5a6207 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,50 +1,50 @@
 %ul.nav.nav-sidebar
   = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do
-    = link_to dashboard_projects_path, title: 'Projects', data: {placement: 'right'} do
+    = link_to dashboard_projects_path, title: 'Projects' do
       = icon('home fw')
       %span
         Projects
   = nav_link(path: 'dashboard#activity') do
-    = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity', data: {placement: 'right'} do
+    = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do
       = icon('dashboard fw')
       %span
         Activity
   = nav_link(controller: :groups) do
-    = link_to dashboard_groups_path, title: 'Groups', data: {placement: 'right'} do
+    = link_to dashboard_groups_path, title: 'Groups' do
       = icon('group fw')
       %span
         Groups
   = nav_link(controller: :milestones) do
-    = link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do
+    = link_to dashboard_milestones_path, title: 'Milestones' do
       = icon('clock-o fw')
       %span
         Milestones
   = nav_link(path: 'dashboard#issues') do
-    = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
+    = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do
       = icon('exclamation-circle fw')
       %span
         Issues
         %span.count= current_user.assigned_issues.opened.count
     = nav_link(path: 'dashboard#merge_requests') do
-      = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
+      = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
         = icon('tasks fw')
         %span
           Merge Requests
           %span.count= current_user.assigned_merge_requests.opened.count
   = nav_link(controller: :snippets) do
-    = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do
+    = link_to dashboard_snippets_path, title: 'Snippets' do
       = icon('clipboard fw')
       %span
         Snippets
   = nav_link(controller: :help) do
-    = link_to help_path, title: 'Help', data: {placement: 'right'} do
+    = link_to help_path, title: 'Help' do
       = icon('question-circle fw')
       %span
         Help
 
   %li.separate-item
   = nav_link(controller: :profile) do
-    = link_to profile_path, title: 'Profile settings', data: {placement: 'bottom'} do
+    = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
       = icon('user fw')
       %span
         Profile Settings
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index 21e565972a730616acc7fa56d19c4fc3b4ad838f..48039ca2918ffaeb1715c7077fbb6f3fa949406a 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -1,21 +1,21 @@
 %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', data: {placement: 'right'} do
+    = link_to explore_root_path, title: 'Projects' do
       = icon('home fw')
       %span
         Projects
   = nav_link(controller: :groups) do
-    = link_to explore_groups_path, title: 'Groups', data: {placement: 'right'} 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', data: {placement: 'right'} 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', data: {placement: 'right'} do
+    = link_to help_path, title: 'Help' do
       = icon('question-circle fw')
       %span
         Help
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 319352876b4ec648b8b1315ad1669790cc979430..68da8d5de2a19ec39b58272c7066f5528907a33b 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,6 +1,6 @@
 %ul.nav.nav-sidebar
   = nav_link do
-    = link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
+    = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
       = icon('caret-square-o-left fw')
       %span
         Go to dashboard
@@ -8,39 +8,39 @@
   %li.separate-item
 
   = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
-    = link_to group_path(@group), title: 'Home', data: {placement: 'right'} do
+    = link_to group_path(@group), title: 'Home' do
       = icon('dashboard fw')
       %span
         Group
   - if can?(current_user, :read_group, @group)
     - if current_user
       = nav_link(controller: [:group, :milestones]) do
-        = link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do
+        = link_to group_milestones_path(@group), title: 'Milestones' do
           = icon('clock-o fw')
           %span
             Milestones
     = nav_link(path: 'groups#issues') do
-      = link_to issues_group_path(@group), title: 'Issues', data: {placement: 'right'} do
+      = link_to issues_group_path(@group), title: 'Issues' do
         = icon('exclamation-circle fw')
         %span
           Issues
           - if current_user
             %span.count= Issue.opened.of_group(@group).count
     = nav_link(path: 'groups#merge_requests') do
-      = link_to merge_requests_group_path(@group), title: 'Merge Requests', data: {placement: 'right'} do
+      = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
         = icon('tasks fw')
         %span
           Merge Requests
           - if current_user
             %span.count= MergeRequest.opened.of_group(@group).count
     = nav_link(controller: [:group_members]) do
-      = link_to group_group_members_path(@group), title: 'Members', data: {placement: 'right'} do
+      = link_to group_group_members_path(@group), title: 'Members' do
         = icon('users fw')
         %span
           Members
     - if can?(current_user, :admin_group, @group)
       = nav_link(html_options: { class: "separate-item" }) do
-        = link_to edit_group_path(@group), title: 'Settings', data: {placement: 'right'} do
+        = link_to edit_group_path(@group), title: 'Settings' do
           = icon ('cogs fw')
           %span
             Settings
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index c8411521f3692f40746882e3da47e8a8611e26e0..56a92fe9103b5c5834925db7dd35e7020272fe05 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,6 +1,6 @@
 %ul.nav.nav-sidebar
   = nav_link do
-    = link_to group_path(@group), title: 'Go to group', data: {placement: 'right'}, class: 'back-link' do
+    = link_to group_path(@group), title: 'Go to group', class: 'back-link' do
       = icon('caret-square-o-left fw')
       %span
         Go to group
@@ -9,12 +9,12 @@
 
   %ul.sidebar-subnav
     = nav_link(path: 'groups#edit') do
-      = link_to edit_group_path(@group), title: 'Group Settings', data: {placement: 'right'} do
+      = link_to edit_group_path(@group), title: 'Group Settings' do
         = icon ('pencil-square-o fw')
         %span
           Group Settings
     = nav_link(path: 'groups#projects') do
-      = link_to projects_group_path(@group), title: 'Projects', data: {placement: 'right'} do
+      = link_to projects_group_path(@group), title: 'Projects' do
         = icon('folder fw')
         %span
           Projects
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 0f3a793e30be18ac993bdc59ee800b60144ae48d..64b30783c0590a881a6b6bad98c1449bc1cb46fa 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,6 +1,6 @@
 %ul.nav.nav-sidebar
   = nav_link do
-    = link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
+    = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
       = icon('caret-square-o-left fw')
       %span
         Go to dashboard
@@ -8,52 +8,52 @@
   %li.separate-item
 
   = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
-    = link_to profile_path, title: 'Profile', data: {placement: 'right'} do
+    = link_to profile_path, title: 'Profile Settings' do
       = icon('user fw')
       %span
         Profile Settings
   = nav_link(controller: [:accounts, :two_factor_auths]) do
-    = link_to profile_account_path, title: 'Account', data: {placement: 'right'} do
+    = link_to profile_account_path, title: 'Account' do
       = icon('gear fw')
       %span
         Account
   = nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new', 'applications#create']) do
-    = link_to applications_profile_path, title: 'Applications', data: {placement: 'right'} do
+    = link_to applications_profile_path, title: 'Applications' do
       = icon('cloud fw')
       %span
         Applications
   = nav_link(controller: :emails) do
-    = link_to profile_emails_path, title: 'Emails', data: {placement: 'right'} do
+    = link_to profile_emails_path, title: 'Emails' do
       = icon('envelope-o fw')
       %span
         Emails
         %span.count= current_user.emails.count + 1
   - unless current_user.ldap_user?
     = nav_link(controller: :passwords) do
-      = link_to edit_profile_password_path, title: 'Password', data: {placement: 'right'} do
+      = link_to edit_profile_password_path, title: 'Password' do
         = icon('lock fw')
         %span
           Password
   = nav_link(controller: :notifications) do
-    = link_to profile_notifications_path, title: 'Notifications', data: {placement: 'right'} do
+    = link_to profile_notifications_path, title: 'Notifications' do
       = icon('inbox fw')
       %span
         Notifications
 
   = nav_link(controller: :keys) do
-    = link_to profile_keys_path, title: 'SSH Keys', data: {placement: 'right'} do
+    = link_to profile_keys_path, title: 'SSH Keys' do
       = icon('key fw')
       %span
         SSH Keys
         %span.count= current_user.keys.count
   = nav_link(controller: :preferences) do
-    = link_to profile_preferences_path, title: 'Preferences', data: {placement: 'right'} do
+    = link_to profile_preferences_path, title: 'Preferences' do
       -# TODO (rspeicher): Better icon?
       = icon('image fw')
       %span
         Preferences
   = nav_link(path: 'profiles#audit_log') do
-    = link_to audit_log_profile_path, title: 'Audit Log', data: {placement: 'right'} do
+    = link_to audit_log_profile_path, title: 'Audit Log' do
       = icon('history fw')
       %span
         Audit Log
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 2b91d7721f932446cd27516eaf810409d8b67657..c0d6202863984b2062eb420475dd3d7874f39cb8 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,13 +1,13 @@
 %ul.nav.nav-sidebar
   - if @project.group
     = nav_link do
-      = link_to group_path(@project.group), title: 'Go to group', data: {placement: 'right'}, class: 'back-link' do
+      = link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do
         = icon('caret-square-o-left fw')
         %span
           Go to group
   - else
     = nav_link do
-      = link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
+      = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
         = icon('caret-square-o-left fw')
         %span
           Go to dashboard
@@ -15,61 +15,54 @@
   %li.separate-item
 
   = nav_link(path: 'projects#show', html_options: {class: 'home'}) do
-    = link_to project_path(@project), title: 'Project', class: 'shortcuts-project', data: {placement: 'right'} do
+    = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
       = icon('home fw')
       %span
         Project
   = nav_link(path: 'projects#activity') do
-    = link_to activity_project_path(@project), title: 'Project Activity', class: 'shortcuts-project-activity', data: {placement: 'right'} do
+    = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
       = icon('dashboard fw')
       %span
         Activity
   - if project_nav_tab? :files
     = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
-      = link_to project_files_path(@project), title: 'Files',  class: 'shortcuts-tree', data: {placement: 'right'} do
+      = link_to project_files_path(@project), title: 'Files',  class: 'shortcuts-tree' do
         = icon('files-o fw')
         %span
           Files
 
   - if project_nav_tab? :commits
-    = nav_link(controller: %w(commit commits compare repositories tags branches releases)) do
-      = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do
+    = nav_link(controller: %w(commit commits compare repositories tags branches releases network)) do
+      = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
         = icon('history fw')
         %span
           Commits
 
   - if project_nav_tab? :builds
     = nav_link(controller: %w(builds)) do
-      = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds', data: {placement: 'right'} do
+      = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
         = icon('cubes fw')
         %span
           Builds
-          %span.count.builds_counter= @project.ci_builds.running_or_pending.count(:all)
-
-  - if project_nav_tab? :network
-    = nav_link(controller: %w(network)) do
-      = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do
-        = icon('code-fork fw')
-        %span
-          Network
+          %span.count.builds_counter= @project.builds.running_or_pending.count(:all)
 
   - if project_nav_tab? :graphs
     = nav_link(controller: %w(graphs)) do
-      = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs',  class: 'shortcuts-graphs', data: {placement: 'right'} do
+      = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs',  class: 'shortcuts-graphs' do
         = icon('area-chart fw')
         %span
           Graphs
 
   - if project_nav_tab? :milestones
     = nav_link(controller: :milestones) do
-      = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones', data: {placement: 'right'} do
+      = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
         = icon('clock-o fw')
         %span
           Milestones
 
   - if project_nav_tab? :issues
     = nav_link(controller: :issues) do
-      = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
+      = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
         = icon('exclamation-circle fw')
         %span
           Issues
@@ -78,7 +71,7 @@
 
   - if project_nav_tab? :merge_requests
     = nav_link(controller: :merge_requests) do
-      = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
+      = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
         = icon('tasks fw')
         %span
           Merge Requests
@@ -86,35 +79,42 @@
 
   - if project_nav_tab? :settings
     = nav_link(controller: [:project_members, :teams]) do
-      = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do
+      = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
         = icon('users fw')
         %span
           Members
 
   - if project_nav_tab? :labels
     = nav_link(controller: :labels) do
-      = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels', data: {placement: 'right'} do
+      = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
         = icon('tags fw')
         %span
           Labels
 
   - if project_nav_tab? :wiki
     = nav_link(controller: :wikis) do
-      = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki', data: {placement: 'right'} do
+      = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
         = icon('book fw')
         %span
           Wiki
 
   - if project_nav_tab? :snippets
     = nav_link(controller: :snippets) do
-      = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets', data: {placement: 'right'} do
+      = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
         = icon('clipboard fw')
         %span
           Snippets
 
   - if project_nav_tab? :settings
     = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
-      = link_to edit_project_path(@project), title: 'Settings', data: {placement: 'right'} do
+      = link_to edit_project_path(@project), title: 'Settings' do
         = icon('cogs fw')
         %span
           Settings
+
+  -# Global shortcut to network page for compatibility
+  - if project_nav_tab? :network
+    %li.hidden
+      = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
+        Network
+
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 377a99e719aa4bfb18784b2699f54c63a0151c39..970da78a5c9ca59abb7eb2271d19718e6b394154 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -1,6 +1,6 @@
 %ul.nav.nav-sidebar
   = nav_link do
-    = link_to project_path(@project), title: 'Go to project', data: {placement: 'right'}, class: 'back-link' do
+    = link_to project_path(@project), title: 'Go to project', class: 'back-link' do
       = icon('caret-square-o-left fw')
       %span
         Go to project
@@ -9,59 +9,44 @@
 
   %ul.sidebar-subnav
     = nav_link(path: 'projects#edit') do
-      = link_to edit_project_path(@project), title: 'Project Settings', data: {placement: 'right'} do
+      = link_to edit_project_path(@project), title: 'Project Settings' do
         = icon('pencil-square-o fw')
         %span
           Project Settings
     = nav_link(controller: :deploy_keys) do
-      = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys', data: {placement: 'right'} do
+      = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
         = icon('key fw')
         %span
           Deploy Keys
     = nav_link(controller: :hooks) do
-      = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks', data: {placement: 'right'} do
+      = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks' do
         = icon('link fw')
         %span
           Web Hooks
     = nav_link(controller: :services) do
-      = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services', data: {placement: 'right'} do
+      = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do
         = icon('cogs fw')
         %span
           Services
     = nav_link(controller: :protected_branches) do
-      = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches', data: {placement: 'right'} do
+      = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
         = icon('lock fw')
         %span
           Protected Branches
 
     - if @project.builds_enabled?
       = nav_link(controller: :runners) do
-        = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners', data: {placement: 'right'} do
+        = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
           = icon('cog fw')
           %span
             Runners
       = nav_link(controller: :variables) do
-        = link_to namespace_project_variables_path(@project.namespace, @project) do
+        = link_to namespace_project_variables_path(@project.namespace, @project), title: 'Variables' do
           = icon('code fw')
           %span
             Variables
       = nav_link path: 'triggers#index' do
-        = link_to namespace_project_triggers_path(@project.namespace, @project) do
+        = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
           = icon('retweet fw')
           %span
             Triggers
-      = nav_link path: 'ci_web_hooks#index' do
-        = link_to namespace_project_ci_web_hooks_path(@project.namespace, @project) do
-          = icon('link fw')
-          %span
-            CI Web Hooks
-      = nav_link path: 'ci_settings#edit' do
-        = link_to edit_namespace_project_ci_settings_path(@project.namespace, @project) do
-          = icon('building fw')
-          %span
-            CI Settings
-      = nav_link controller: 'ci_services' do
-        = link_to namespace_project_ci_services_path(@project.namespace, @project) do
-          = icon('share fw')
-          %span
-            CI Services
diff --git a/app/views/ci/notify/build_fail_email.html.haml b/app/views/notify/build_fail_email.html.haml
similarity index 69%
rename from app/views/ci/notify/build_fail_email.html.haml
rename to app/views/notify/build_fail_email.html.haml
index b0aaea89075b1e29ce5a707e40e294c667d8d3b3..f4e9749e5c7badec4d08577ded0e4a01a03374e5 100644
--- a/app/views/ci/notify/build_fail_email.html.haml
+++ b/app/views/notify/build_fail_email.html.haml
@@ -1,13 +1,13 @@
 - content_for :header do
   %h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
-    GitLab CI (build failed)
+    GitLab (build failed)
 %h3
   Project:
   = link_to ci_project_url(@project) do
     = @project.name
 
 %p
-  Commit: #{link_to @build.short_sha, namespace_project_commit_path(@build.gl_project.namespace, @build.gl_project, @build.sha)}
+  Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.project.namespace, @build.project, @build.sha)}
 %p
   Author: #{@build.commit.git_author_name}
 %p
@@ -20,4 +20,4 @@
   Message: #{@build.commit.git_commit_message}
 
 %p
-  Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)}
+  Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
diff --git a/app/views/ci/notify/build_fail_email.text.erb b/app/views/notify/build_fail_email.text.erb
similarity index 74%
rename from app/views/ci/notify/build_fail_email.text.erb
rename to app/views/notify/build_fail_email.text.erb
index 17a3b9b1d33df14cd27f07d35f9876323f57c81d..675acea60a16d01fab25fd3c59a16b64f5052b5c 100644
--- a/app/views/ci/notify/build_fail_email.text.erb
+++ b/app/views/notify/build_fail_email.text.erb
@@ -8,4 +8,4 @@ Stage:    <%= @build.stage %>
 Job:      <%= @build.name %>
 Message:  <%= @build.commit.git_commit_message %>
 
-Url:      <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %>
+Url:      <%= namespace_project_build_url(@build.project.namespace, @build.project, @build) %>
diff --git a/app/views/ci/notify/build_success_email.html.haml b/app/views/notify/build_success_email.html.haml
similarity index 68%
rename from app/views/ci/notify/build_success_email.html.haml
rename to app/views/notify/build_success_email.html.haml
index 24c439e50eb39fa3a0a79fbaebe8ba8bf3b2dc03..8b004d34ccaa89aaecf812ccf871590e5e4860a6 100644
--- a/app/views/ci/notify/build_success_email.html.haml
+++ b/app/views/notify/build_success_email.html.haml
@@ -1,6 +1,6 @@
 - content_for :header do
   %h1{style: "background: #38CF5B; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
-    GitLab CI (build successful)
+    GitLab (build successful)
 
 %h3
   Project:
@@ -8,7 +8,7 @@
     = @project.name
 
 %p
-  Commit: #{link_to @build.short_sha, namespace_project_commit_path(@build.gl_project.namespace, @build.gl_project, @build.sha)}
+  Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.project.namespace, @build.project, @build.sha)}
 %p
   Author: #{@build.commit.git_author_name}
 %p
@@ -21,4 +21,4 @@
   Message: #{@build.commit.git_commit_message}
 
 %p
-  Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)}
+  Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
diff --git a/app/views/ci/notify/build_success_email.text.erb b/app/views/notify/build_success_email.text.erb
similarity index 74%
rename from app/views/ci/notify/build_success_email.text.erb
rename to app/views/notify/build_success_email.text.erb
index bc8b978c3d75b8a2a03acb3bf152e84ce5e18888..747da44acae8d1e6c62ee9283f95494fea0ed433 100644
--- a/app/views/ci/notify/build_success_email.text.erb
+++ b/app/views/notify/build_success_email.text.erb
@@ -8,4 +8,4 @@ Stage:    <%= @build.stage %>
 Job:      <%= @build.name %>
 Message:  <%= @build.commit.git_commit_message %>
 
-Url:      <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %>
+Url:      <%= namespace_project_build_url(@build.project.namespace, @build.project, @build) %>
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 12f83aae04be374b1771faa082a52e3045e24b32..4361f67a74d2db43fe2423de0e2465030b978a4b 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -1,30 +1,32 @@
-%h3 #{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}
+%h3
+  #{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name}
+  at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))}
 
-- if @compare
-  - if @reverse_compare
+- if @message.compare
+  - if @message.reverse_compare?
     %p
       %strong WARNING:
       The push did not contain any new commits, but force pushed to delete the commits and changes below.
 
   %h4
-    = @reverse_compare ? "Deleted commits:" : "Commits:"
+    = @message.reverse_compare? ? "Deleted commits:" : "Commits:"
 
   %ul
-    - @commits.each do |commit|
+    - @message.commits.each do |commit|
       %li
-        %strong #{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)}
+        %strong #{link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))}
         %div
           %span by #{commit.author_name}
           %i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
         %pre.commit-message 
           = commit.safe_message
 
-  %h4 #{pluralize @diffs.count, "changed file"}:
+  %h4 #{pluralize @message.diffs_count, "changed file"}:
 
   %ul
-    - @diffs.each_with_index do |diff, i|
+    - @message.diffs.each_with_index do |diff, i|
       %li.file-stats
-        %a{href: "#{@target_url if @disable_diffs}#diff-#{i}" }
+        %a{href: "#{@message.target_url if @message.disable_diffs?}#diff-#{i}" }
           - if diff.deleted_file
             %span.deleted-file
               &minus;
@@ -40,11 +42,11 @@
           - else
             = diff.new_path
 
-  - unless @disable_diffs
+  - unless @message.disable_diffs?
     %h4 Changes:
-    - @diffs.each_with_index do |diff, i|
+    - @message.diffs.each_with_index do |diff, i|
       %li{id: "diff-#{i}"}
-        %a{href: @target_url + "#diff-#{i}"}
+        %a{href: @message.target_url + "#diff-#{i}"}
           - if diff.deleted_file
             %strong
               = diff.old_path
@@ -62,5 +64,5 @@
         = color_email_diff(diff.diff)
         %br
 
-  - if @compare.timeout
+  - if @message.compare_timeout
     %h5 Huge diff. To prevent performance issues changes are hidden
diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml
index 97a176ed2a3800577ce0ec8340c0c6d7428c41dc..aa0e263b6dfa96b6d81e160cdb4eb66aa5c28586 100644
--- a/app/views/notify/repository_push_email.text.haml
+++ b/app/views/notify/repository_push_email.text.haml
@@ -1,21 +1,21 @@
-#{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{@project.name_with_namespace}
-- if @compare
+#{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name} at #{@message.project_name_with_namespace}
+- if @message.compare
   \
   \
-  - if @reverse_compare
+  - if @message.reverse_compare?
     WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below.
     \
     \
-  = @reverse_compare ? "Deleted commits:" : "Commits:"
-  - @commits.each do |commit|
+  = @message.reverse_compare? ? "Deleted commits:" : "Commits:"
+  - @message.commits.each do |commit|
     #{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
     #{commit.safe_message}
     \- - - - -
   \
   \
-  #{pluralize @diffs.count, "changed file"}:
+  #{pluralize @message.diffs_count, "changed file"}:
   \
-  - @diffs.each do |diff|
+  - @message.diffs.each do |diff|
     - if diff.deleted_file
       \- − #{diff.old_path}
     - elsif diff.renamed_file
@@ -24,11 +24,11 @@
       \- + #{diff.new_path}
     - else
       \- #{diff.new_path}
-  - unless @disable_diffs
+  - unless @message.disable_diffs?
     \
     \
     Changes:
-    - @diffs.each do |diff|
+    - @message.diffs.each do |diff|
       \
       \=====================================
       - if diff.deleted_file
@@ -39,11 +39,11 @@
         = diff.new_path
       \=====================================
       != diff.diff
-  - if @compare.timeout
+  - if @message.compare_timeout
     \
     \
     Huge diff. To prevent performance issues it was hidden
-  - if @target_url
+  - if @message.target_url
     \
     \
-    View it on GitLab: #{@target_url}
+    View it on GitLab: #{@message.target_url}
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index cd7b1b0fe0338dd82b35b41a2a8bc485b7bc7891..17e47c622ceec6d1645f6cda4cd60f3e926d1519 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -23,11 +23,14 @@
           %p.cgray
             - if current_user.private_token
               = text_field_tag "token", current_user.private_token, class: "form-control"
-              %div
-                = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
             - else
               %span You don`t have one yet. Click generate to fix it.
-              = f.submit 'Generate', class: "btn btn-default btn-build-token"
+
+        .form-actions
+          - if current_user.private_token
+            = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default"
+          - else
+            = f.submit 'Generate', class: "btn btn-default"
 
   - unless current_user.ldap_user?
     .panel.panel-default
@@ -54,7 +57,8 @@
           %p
             Each time you log in you’ll be required to provide your username and
             password as usual, plus a randomly-generated code from your phone.
-          %div
+
+          .form-actions
             = link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
 
   - if button_based_providers.any?
@@ -81,15 +85,16 @@
           %p
             Changing your username will change path to all personal projects!
           %div
-            = f.text_field :username, required: true, class: 'form-control'
+            .input-group
+              .input-group-addon
+                = "#{root_url}u/"
+              = f.text_field :username, required: true, class: 'form-control'
             &nbsp;
           .loading-gif.hide
             %p
               = icon('spinner spin')
               Saving new username
-          %p.light
-            = user_url(@user)
-          %div
+          .form-actions
             = f.submit 'Save username', class: "btn btn-warning"
 
   - if signup_enabled?
@@ -104,7 +109,8 @@
             - rp = current_user.personal_projects.count
             - unless rp.zero?
               %li #{pluralize rp, 'personal project'} will be removed and cannot be restored
-          = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
+          .form-actions
+            = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
         - else
           - if @user.solo_owned_groups.present?
             %p
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
index 2342936a5d50db91cfc70e749a4faf6f635f0219..0436c2213da003078022bc0462f2cdabf033f6ed 100644
--- a/app/views/profiles/applications.html.haml
+++ b/app/views/profiles/applications.html.haml
@@ -15,24 +15,25 @@
       .pull-right
         = link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
     - if @applications.any?
-      %table.table.table-striped
-        %thead
-          %tr
-            %th Name
-            %th Callback URL
-            %th Clients
-            %th
-            %th
-        %tbody
-          - @applications.each do |application|
-            %tr{:id => "application_#{application.id}"}
-              %td= link_to application.name, oauth_application_path(application)
-              %td
-                - application.redirect_uri.split.each do |uri|
-                  %div= uri
-              %td= application.access_tokens.count
-              %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm'
-              %td= render 'doorkeeper/applications/delete_form', application: application
+      .table-holder
+        %table.table.table-striped
+          %thead
+            %tr
+              %th Name
+              %th Callback URL
+              %th Clients
+              %th
+              %th
+          %tbody
+            - @applications.each do |application|
+              %tr{:id => "application_#{application.id}"}
+                %td= link_to application.name, oauth_application_path(application)
+                %td
+                  - application.redirect_uri.split.each do |uri|
+                    %div= uri
+                %td= application.access_tokens.count
+                %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm'
+                %td= render 'doorkeeper/applications/delete_form', application: application
 
 .oauth-authorized-applications.prepend-top-20
   - if user_oauth_applications?
@@ -40,29 +41,30 @@
       Authorized applications
 
   - if @authorized_tokens.any?
-    %table.table.table-striped
-      %thead
-        %tr
-          %th Name
-          %th Authorized At
-          %th Scope
-          %th
-      %tbody
-        - @authorized_apps.each do |app|
-          - token = app.authorized_tokens.order('created_at desc').first
-          %tr{:id => "application_#{app.id}"}
-            %td= app.name
-            %td= token.created_at
-            %td= token.scopes
-            %td= render 'doorkeeper/authorized_applications/delete_form', application: app
-        - @authorized_anonymous_tokens.each do |token|
+    .table-holder
+      %table.table.table-striped
+        %thead
           %tr
-            %td
-              Anonymous
-              %div.help-block
-                %em Authorization was granted by entering your username and password in the application.
-            %td= token.created_at
-            %td= token.scopes
-            %td= render 'doorkeeper/authorized_applications/delete_form', token: token
+            %th Name
+            %th Authorized At
+            %th Scope
+            %th
+        %tbody
+          - @authorized_apps.each do |app|
+            - token = app.authorized_tokens.order('created_at desc').first
+            %tr{:id => "application_#{app.id}"}
+              %td= app.name
+              %td= token.created_at
+              %td= token.scopes
+              %td= render 'doorkeeper/authorized_applications/delete_form', application: app
+          - @authorized_anonymous_tokens.each do |token|
+            %tr
+              %td
+                Anonymous
+                %div.help-block
+                  %em Authorization was granted by entering your username and password in the application.
+              %td= token.created_at
+              %td= token.scopes
+              %td= render 'doorkeeper/authorized_applications/delete_form', token: token
   - else
     %p.light You don't have any authorized applications
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index b76a5b636acaa9ddd544029997ea14c3ab5a500a..2a8800de60e78888b069e8b9b74d5d6e29735534 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -1,5 +1,5 @@
 %div
-  = form_for [:profile, @key], html: { class: 'form-horizontal' } do |f|
+  = form_for [:profile, @key], html: { class: 'form-horizontal js-requires-input' } do |f|
     - if @key.errors.any?
       .alert.alert-danger
         %ul
@@ -9,12 +9,11 @@
     .form-group
       = f.label :key, class: 'control-label'
       .col-sm-10
-        = f.text_area :key, class: "form-control", rows: 8
+        = f.text_area :key, class: "form-control", rows: 8, autofocus: true, required: true
     .form-group
       = f.label :title, class: 'control-label'
-      .col-sm-10= f.text_field :title, class: "form-control"
+      .col-sm-10= f.text_field :title, class: "form-control", required: true
 
     .form-actions
       = f.submit 'Add key', class: "btn btn-create"
       = link_to "Cancel", profile_keys_path, class: "btn btn-cancel"
-
diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml
index ef0075aad3b20598c45a21fed3453a8cd65131b6..8c9d546af4c395a459849a8c32628e070c3fc434 100644
--- a/app/views/profiles/keys/_key_table.html.haml
+++ b/app/views/profiles/keys/_key_table.html.haml
@@ -1,6 +1,6 @@
 - is_admin = defined?(admin) ? true : false
-.panel.panel-default
-  - if @keys.any?
+- if @keys.any?
+  .table-holder
     %table.table
       %thead.panel-heading
         %tr
@@ -11,9 +11,9 @@
       %tbody
         - @keys.each do |key|
           = render 'profiles/keys/key', key: key, is_admin: is_admin
-  - else
-    .nothing-here-block
-      - if is_admin
-        User has no ssh keys
-      - else
-        There are no SSH keys with access to your account.
+- else
+  .nothing-here-block
+    - if is_admin
+      User has no ssh keys
+    - else
+      There are no SSH keys with access to your account.
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 14adba1c797e61b53f298e769515a11d4738da4b..17a4195030e767aa9b4384c9d8a38b315d41129d 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -3,7 +3,9 @@
 
 .gray-content-block.top-block
   .pull-right
-    = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
+    = link_to new_profile_key_path, class: "btn btn-new" do
+      = icon('plus')
+      Add SSH Key
   .oneline
     Before you can add an SSH key you need to
     = link_to "generate it.", help_page_path("ssh", "README")
diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml
index 2bf207a322161b7f2e2e5864a1083aee1e5a28ef..13a18269d11b57c0dc2fd6ee3ca257ce59cd4306 100644
--- a/app/views/profiles/keys/new.html.haml
+++ b/app/views/profiles/keys/new.html.haml
@@ -9,9 +9,9 @@
   $('#key_key').on('focusout', function(){
     var title = $('#key_title'),
         val      = $('#key_key').val(),
-        comment = val.match(/^\S+ \S+ (.+)$/);
+        comment = val.match(/^\S+ \S+ (.+)\n?$/);
 
     if( comment && comment.length > 1 && title.val() == '' ){
-      $('#key_title').val( comment[1] );
+      $('#key_title').val( comment[1] ).change();
     }
   });
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 8eebd96b674f57a9a6a19ffaeb68626826339200..0bcadc965fa00568bbb05ec4bfd3771d11e86ac1 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -50,12 +50,10 @@
             Watch
           %p You will receive notifications for any activity
 
-  .form-actions
+  .gray-content-block
     = f.submit 'Save changes', class: "btn btn-create"
 
-.clearfix
-  %hr
-.row.all-notifications
+.row.all-notifications.prepend-top-default
   .col-md-6
     %p
       You can also specify notification level per group or per project.
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index cc41d7dd8130ab787d023d41bf01d32e1529bc9c..877589dc390ba24500712b3701b0ba7cffa24bec 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -54,4 +54,4 @@
           .help-block
             Choose what content you want to see on a project's home page.
     .panel-footer
-      = f.submit 'Save', class: 'btn btn-save'
+      = f.submit 'Save changes', class: 'btn btn-save'
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index ac7355dde1fc1f6a112638ff5ed62dc2d4148e5f..9459d8a6295951bf00f446e302c1895a9613f45e 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -43,7 +43,7 @@
       .form-group
         = f.label :public_email, class: "control-label"
         .col-sm-10
-          = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show in profile'}, class: "form-control"
+          = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show on profile'}, class: "select2"
           %span.help-block This email will be displayed on your public profile.
       .form-group
         = f.label :skype, class: "control-label"
@@ -96,8 +96,6 @@
               = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
 
 
-  .row
-    .col-md-7
-      .form-group
-        .col-sm-offset-2.col-sm-10
-          = f.submit 'Save changes', class: "btn btn-success"
+  .form-actions
+    = f.submit 'Save changes', class: "btn btn-success"
+    = link_to "Cancel", user_path(current_user), class: "btn btn-cancel"
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index 92dc58c10d71f643e733a15773f2c02e39141395..1a5b6efce3558e457a129203bff3fad0589f28a2 100644
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -38,3 +38,4 @@
       = text_field_tag :pin_code, nil, class: "form-control", required: true, autofocus: true
   .form-actions
     = submit_tag 'Submit', class: 'btn btn-success'
+    = link_to 'Configure it later', skip_profile_two_factor_auth_path, :method => :patch,  class: 'btn btn-cancel' if two_factor_skippable?
diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml
index 35f7e7bb34bcfc190ca41af464136482bba56ebd..640612ca433d0ac29e6bb6381d914c001a6925cf 100644
--- a/app/views/projects/_commit_button.html.haml
+++ b/app/views/projects/_commit_button.html.haml
@@ -1,6 +1,8 @@
 .form-actions
-  .commit-button-annotation
-    = button_tag 'Commit Changes',
-                 class: 'btn commit-btn js-commit-button btn-create'
+  = button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create'
   = link_to 'Cancel', cancel_path,
             class: 'btn btn-cancel', data: {confirm: leave_edit_message}
+
+  - unless can?(current_user, :push_code, @project)
+    .inline.prepend-left-10
+      = commit_in_fork_help
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 88d54bf6f21466da2b6c94374c7d84a317a0e26c..e92115b9b987ba29111ba28c8f6be4991c7816c6 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,5 +1,5 @@
 - empty_repo = @project.empty_repo?
-.project-home-panel.clearfix{:class => ("empty-project" if empty_repo)}
+.project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)}
   .project-identicon-holder
     = project_icon(@project, alt: '', class: 'project-avatar avatar s90')
   .project-home-desc
@@ -12,18 +12,32 @@
         Forked from
         = link_to project_path(forked_from_project) do
           = forked_from_project.namespace.try(:name)
+  .cover-controls.left
+    .visibility-level-label.has_tooltip{title: project_visibility_level_description(@project.visibility_level), data: { container: 'body' } }
+      = visibility_level_icon(@project.visibility_level, fw: false)
+      = visibility_level_label(@project.visibility_level)
 
-
+  .cover-controls
+    - if can?(current_user, :admin_project, @project)
+      = link_to edit_project_path(@project), class: 'btn btn-gray' do
+        = icon('pencil')
+    - if current_user
+      &nbsp;
+      = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), class: 'btn btn-gray' do
+        = icon('rss')
 
   .project-repo-buttons
-    .split-one
+    .split-one.count-buttons
       = render 'projects/buttons/star'
       = render 'projects/buttons/fork'
 
     = render "shared/clone_panel"
-    
+
     .split-repo-buttons
       = render "projects/buttons/download"
       = render 'projects/buttons/dropdown'
 
     = render 'projects/buttons/notifications'
+
+:coffeescript
+  new Star()
\ No newline at end of file
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
index 7e1ee2b7fc1ab92aa0c46a0f60d45528a016e883..386d72e77872333baab57b66ac26386bb24bcbd1 100644
--- a/app/views/projects/_last_commit.html.haml
+++ b/app/views/projects/_last_commit.html.haml
@@ -3,7 +3,7 @@
   - if ci_commit
     = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
       = ci_status_icon(ci_commit)
-      = ci_commit.status
+      = ci_status_label(ci_commit)
 
   = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
   = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 7b21095ea3e8637688f32cbf4ef94bf2e6f4379b..54c818baaf41168f2673bda0a3b9961c8d9f514e 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -5,20 +5,21 @@
         %a.js-md-write-button(href="#md-write-holder" tabindex="-1")
           Write
       %li
-        %a.js-md-preview-button(href="md-preview-holder" tabindex="-1")
+        %a.js-md-preview-button(href="#md-preview-holder" tabindex="-1")
           Preview
 
-    - if defined?(referenced_users) && referenced_users
-      %span.referenced-users.pull-left.hide
+  %div
+    .md-write-holder
+      = yield
+    .md.md-preview-holder.hide
+      .js-md-preview{class: (preview_class if defined?(preview_class))}
+
+  - if defined?(referenced_users) && referenced_users
+    %div.referenced-users.hide
+      %span
         = icon('exclamation-triangle')
         You are about to add
         %strong
           %span.js-referenced-users-count 0
           people
         to the discussion. Proceed with caution.
-
-  %div
-    .md-write-holder
-      = yield
-    .md.md-preview-holder.hide
-      .js-md-preview{class: (preview_class if defined?(preview_class))}
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index b5ef0aca54035a77f2e684506350124d77561b99..d1191928d4fd36811b80baa02cccbb4c09cc6961 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -7,15 +7,16 @@
       = cache(readme_cache_key) do
         = render_readme(readme)
 - else
-  %h3.page-title
-    This project does not have README yet
-  - if can?(current_user, :push_code, @project)
-    %p.slead
-      A
-      %code README
-      file contains information about other files in a repository and is commonly
-      distributed with computer software, forming part of its documentation.
-      %br
-      We recommend you to
-      = link_to "add README", new_readme_path, class: 'underlined-link'
-      file to the repository and GitLab will render it here instead of this message.
+  .gray-content-block.second-block.center
+    %h3.page-title
+      This project does not have README yet
+    - if can?(current_user, :push_code, @project)
+      %p
+        A
+        %code README
+        file contains information about other files in a repository and is commonly
+        distributed with computer software, forming part of its documentation.
+      %p
+        We recommend you to
+        = link_to "add README", new_readme_path, class: 'underlined-link'
+        file to the repository and GitLab will render it here instead of this message.
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 6518c4173e1a0ff47bef67e37b5804b3d5cd5cfd..8d9ec068a4358a38ba030fb9665033a67d9ea643 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -6,7 +6,7 @@
 #tree-holder.tree-holder
   .file-holder
     .file-title
-      %i.fa.fa-file
+      = blob_icon @blob.mode, @blob.name
       %strong
         = @path
       %small= number_to_human_size @blob.size
@@ -43,4 +43,3 @@
                   - blame_group[:lines].each do |line|
                     :erb
                       <%= highlight(@blob.name, line, nowrap: true, continue: true).html_safe %>
-
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index ba3e0c3c590b1759c8a68f40be3bc106adf52b98..cdac50f7a8d3a5c7c14d57659c38b114ae8262e9 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -1,9 +1,8 @@
 .btn-group.tree-btn-group
-  = edit_blob_link(@project, @ref, @path)
   = link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id),
       class: 'btn btn-sm', target: '_blank'
   -# only show normal/blame view links for text files
-  - if @blob.text?
+  - if blob_text_viewable?(@blob)
     - if current_page? namespace_project_blame_path(@project.namespace, @project, @id)
       = link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id),
           class: 'btn btn-sm'
@@ -12,11 +11,11 @@
           class: 'btn btn-sm' unless @blob.empty?
   = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id),
       class: 'btn btn-sm'
-  - if @ref != @commit.sha
-    = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
-        tree_join(@commit.sha, @path)), class: 'btn btn-sm'
+  = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
+      tree_join(@commit.sha, @path)), class: 'btn btn-sm'
 
-- if allowed_tree_edit?
+- if current_user
   .btn-group{ role: "group" }
-    %button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace
-    %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete
+    = edit_blob_link
+    = replace_blob_link
+    = delete_blob_link
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 42f632b38efea9748a4cf361a96750bab1a46fab..2a3315da3dbffa54200168ea6b7801a8a316860e 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -29,10 +29,12 @@
       %strong
         = blob.name
       %small
-        = number_to_human_size(blob.size)
+        = number_to_human_size(blob_size(blob))
       .file-actions.hidden-xs
         = render "actions"
-    - if blob.text?
+    - if blob.lfs_pointer?
+      = render "download", blob: blob
+    - elsif blob.text?
       = render "text", blob: blob
     - elsif blob.image?
       = render "image", blob: blob
diff --git a/app/views/projects/blob/_download.html.haml b/app/views/projects/blob/_download.html.haml
index f2c5e95ecf4252d8612c8399d1ff2525b2e4907c..7908fcae3de1db6f9c32aae52a2111a4c9f19799 100644
--- a/app/views/projects/blob/_download.html.haml
+++ b/app/views/projects/blob/_download.html.haml
@@ -4,4 +4,4 @@
       %h1.light
         %i.fa.fa-download
       %h4
-        Download (#{number_to_human_size blob.size})
+        Download (#{number_to_human_size blob_size(blob)})
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index f1ad0c3c403ca45338dcace3d94e2fe0dfda7230..10b02813733de0af679b8b8b281e6e020ede97a8 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -1,23 +1,22 @@
-.file-holder.file
-  .file-title
+.file-holder.file.append-bottom-default
+  .file-title.clearfix
     .editor-ref
-      %i.fa.fa-code-fork
+      = icon('code-fork')
       = ref
     %span.editor-file-name
-      - if @path
-        %span.monospace
-          = @path
+      = @path
 
-      - if current_action?(:new) || current_action?(:create)
+    - if current_action?(:new) || current_action?(:create)
+      %span.editor-file-name
         \/
-        = text_field_tag 'file_name', params[:file_name], placeholder: "File name",
-          required: true, class: 'form-control new-file-name js-quick-submit'
-      .pull-right
-        = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
+      = text_field_tag 'file_name', params[:file_name], placeholder: "File name",
+        required: true, class: 'form-control new-file-name js-quick-submit'
+
+    .pull-right
+      = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
 
   .file-content.code
-    %pre.js-edit-mode-pane#editor
-      = params[:content] || local_assigns[:blob_data]
+    %pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]}
     - if local_assigns[:path]
       .js-edit-mode-pane#preview.hide
         .center
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index 13b5ffd17ff1c69667076a53079568cc8abe20fa..084608bbba38c7a5f64fb923ec7ce9e868b88a86 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -5,19 +5,21 @@
         %a.close{href: "#", "data-dismiss" => "modal"} ×
         %h3.page-title Create New Directory
       .modal-body
-        = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form' do
+        = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-requires-input' do
           .form-group
-            = label_tag :dir_name, 'Directory Name', class: 'control-label'
+            = label_tag :dir_name, 'Directory name', class: 'control-label'
             .col-sm-10
-              = text_field_tag :dir_name, params[:dir_name], placeholder: "Directory name", required: true, class: 'form-control'
+              = text_field_tag :dir_name, params[:dir_name], required: true, class: 'form-control'
 
           = render 'shared/new_commit_form', placeholder: "Add new directory"
 
-          .form-group
-            .col-sm-offset-2.col-sm-10
-              = submit_tag "Create directory", class: 'btn btn-primary btn-create'
-              = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+          .form-actions
+            = submit_tag "Create directory", class: 'btn btn-create'
+            = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+
+            - unless can?(current_user, :push_code, @project)
+              .inline.prepend-left-10
+                = commit_in_fork_help
 
 :javascript
-  disableButtonIfAnyEmptyField($(".js-create-dir-form"), ".form-control", ".btn-create");
   new NewCommitForm($('.js-create-dir-form'))
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 3bb61f0c9444a90a50f32dccbb90eeaed7521d37..676924dc6ca2d366ba98717f882c20a4b837682d 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -16,10 +16,14 @@
 
           = render 'shared/new_commit_form', placeholder: placeholder
 
-          .form-group
-            .col-sm-offset-2.col-sm-10
-              = button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all'
-              = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+          .form-actions
+            = button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all'
+            = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+
+            - unless can?(current_user, :push_code, @project)
+              .inline.prepend-left-10
+                = commit_in_fork_help
+
 
 :javascript
   disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 56745165251993c750590394f44ce24ecb5e0820..09fa148b129b03ec047f78748462478afe4784df 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -5,12 +5,12 @@
   %ul.center-top-menu.no-bottom.js-edit-mode
     %li.active
       = link_to '#editor' do
-        %i.fa.fa-edit
-        Edit file
+        = icon('edit')
+        Edit File
 
     %li
       = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
-        %i.fa.fa-eye
+        = icon('eye')
         = editing_preview_title(@blob.name)
 
   = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input js-edit-blob-form') do
@@ -20,7 +20,7 @@
     = hidden_field_tag 'last_commit', @last_commit
     = hidden_field_tag 'content', '', id: "file-content"
     = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
-    = render 'projects/commit_button', ref: @ref, cancel_path: @after_edit_path
+    = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
 
 :javascript
   blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 1ff68005450841e4a3aeea889b633dc9fa6007d2..167fa61518277a9a0e0cf4301a2b739cfde16508 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -1,9 +1,8 @@
 - page_title "New File", @path.presence, @ref
 = render "header_title"
 
-.gray-content-block.top-block
-  %h3.page-title
-    Create New File
+%h3.page-title
+  New File
 
 .file-editor
   = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-requires-input') do
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index b7276868ce67da31a75007d4ee7811c74baedb8d..6988039b6c7233e30de71f7fc300fa7a949b18d0 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -6,7 +6,7 @@
 %div#tree-holder.tree-holder
   = render 'blob', blob: @blob
 
-- if allowed_tree_edit?
+- if can_edit_blob?(@blob)
   = render 'projects/blob/remove'
 
   - title = "Replace #{@blob.name}"
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 3f95e2a1bf6ec42d1d9dc5cde598f2acb844e0a7..5081bae6801d0cdde8a0060374f328e8a2ab23dc 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -3,17 +3,17 @@
   %div
     = link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
       %strong.str-truncated= branch.name
-      &nbsp;
-      - if branch.name == @repository.root_ref
-        %span.label.label-primary default
-      - elsif @repository.merged_to_root_ref? branch.name
-        %span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
-          merged
+    &nbsp;
+    - if branch.name == @repository.root_ref
+      %span.label.label-primary default
+    - elsif @repository.merged_to_root_ref? branch.name
+      %span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
+        merged
 
-      - if @project.protected_branch? branch.name
-        %span.label.label-success
-          %i.fa.fa-lock
-          protected
+    - if @project.protected_branch? branch.name
+      %span.label.label-success
+        %i.fa.fa-lock
+        protected
     .controls.hidden-xs
       - if create_mr_button?(@repository.root_ref, branch.name)
         = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-grouped btn-xs' do
@@ -26,7 +26,7 @@
           Compare
 
       - if can_remove_branch?(@project, branch.name)
-        = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row', method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" }, remote: true do
+        = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
           = icon("trash-o")
 
   - if commit
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index 22d77dda938d9713c2dfd6b50fc0a18ba4f602a1..9fe65cbb104997e6a8aad64404ddb7dd620cf53a 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"
+  = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), 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 03ade02a0c802bc20698069d7a65be382f582f6f..204def6079426ffd6933849260ebcce558405a9e 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -5,7 +5,7 @@
   .pull-right
     - if can? current_user, :push_code, @project
       = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
-        %i.fa.fa-add-sign
+        = icon('plus')
         New branch
       &nbsp;
     .dropdown.inline
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index f5577042ca4e4c7de15575ad79700b62daf5d3c5..c659af6338c477dacc83c28417567c7064f20c0b 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -6,25 +6,25 @@
     %button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
     = @error
 %h3.page-title
-  %i.fa.fa-code-fork
-  New branch
-= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-requires-input" do
+  New Branch
+%hr
+
+= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-create-branch-form js-requires-input" do
   .form-group
-    = label_tag :branch_name, 'Name for new branch', class: 'control-label'
+    = label_tag :branch_name, nil, class: 'control-label'
     .col-sm-10
-      = text_field_tag :branch_name, params[:branch_name], placeholder: 'enter new branch name', required: true, tabindex: 1, class: 'form-control'
+      = text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control js-branch-name'
+      .help-block.text-danger.js-branch-name-error
   .form-group
     = label_tag :ref, 'Create from', class: 'control-label'
     .col-sm-10
-      = text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control'
+      = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
+      .help-block Existing branch name, tag, or commit SHA
   .form-actions
     = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
     = link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel'
 
 :javascript
-  var availableTags = #{@project.repository.ref_names.to_json};
+  var availableRefs = #{@project.repository.ref_names.to_json};
 
-  $("#ref").autocomplete({
-    source: availableTags,
-    minLength: 1
-  });
+  new NewBranchForm($('.js-create-branch-form'), availableRefs)
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index dab7164153ff119783297a10e48d2f53794aff2e..1a26908ab11e13e8b8c6b4ceb3e1ec48caef8a8f 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -3,26 +3,29 @@
 
 .project-issuable-filter
   .controls
-    - if @ci_project && current_user && can?(current_user, :manage_builds, @project)
+    - if can?(current_user, :manage_builds, @project)
       .pull-left.hidden-xs
         - if @all_builds.running_or_pending.any?
-          = link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
+          = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
 
   %ul.center-top-menu
     %li{class: ('active' if @scope.nil?)}
       = link_to project_builds_path(@project) do
         Running
-        %span.badge.js-running-count= @all_builds.running_or_pending.count(:id)
+        %span.badge.js-running-count
+          = number_with_delimiter(@all_builds.running_or_pending.count(:id))
 
     %li{class: ('active' if @scope == 'finished')}
       = link_to project_builds_path(@project, scope: :finished) do
         Finished
-        %span.badge.js-running-count= @all_builds.finished.count(:id)
+        %span.badge.js-running-count
+          = number_with_delimiter(@all_builds.finished.count(:id))
 
     %li{class: ('active' if @scope == 'all')}
       = link_to project_builds_path(@project, scope: :all) do
         All
-        %span.badge.js-totalbuilds-count= @all_builds.count(:id)
+        %span.badge.js-totalbuilds-count
+          = number_with_delimiter(@all_builds.count(:id))
 
 .gray-content-block
   #{(@scope || 'running').capitalize} builds from this project
@@ -37,7 +40,7 @@
         %thead
           %tr
             %th Status
-            %th Build ID
+            %th Runner
             %th Commit
             %th Ref
             %th Stage
@@ -50,4 +53,3 @@
           = render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, allow_retry: true
 
     = paginate @builds, theme: 'gitlab'
-
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 907e1ce10bddaf600fa7d406abad12505e164b44..5b7ecce86ab2b9041019e526650a50e45eb124a0 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -1,17 +1,20 @@
-- page_title "#{@build.name} (#{@build.id})", "Builds"
+- page_title "#{@build.name} (##{@build.id})", "Builds"
 = render "header_title"
 
 .build-page
-  .gray-content-block
+  .gray-content-block.top-block
     Build ##{@build.id} for commit
-    %strong.monospace
-      = link_to @build.commit.short_sha, ci_status_path(@build.commit)
+    %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
     from
     = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
+    - merge_request = @build.merge_request
+    - if merge_request
+      via
+      = link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request)
 
   #up-build-trace
   - if @commit.matrix_for_ref?(@build.ref)
-    %ul.center-top-menu.build-top-menu
+    %ul.center-top-menu.no-top.no-bottom
       - @commit.latest_builds_for_ref(@build.ref).each do |build|
         %li{class: ('active' if build == @build) }
           = link_to namespace_project_build_path(@project.namespace, @project, build) do
@@ -22,7 +25,6 @@
               - else
                 = build.id
 
-
       - if @build.retried?
         %li.active
           %a
@@ -31,7 +33,7 @@
             %i.fa.fa-warning
             This build was retried.
 
-  .gray-content-block.second-block
+  .gray-content-block.middle-block
     .build-head
       .clearfix
         = ci_status_with_icon(@build.status)
@@ -58,7 +60,7 @@
 
           %br
           Go to
-          = link_to namespace_project_runners_path(@build.gl_project.namespace, @build.gl_project) do
+          = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do
             Runners page
 
   .row.prepend-top-default
@@ -115,7 +117,7 @@
         %p
           %span.attr-name Runner:
           - if @build.runner && current_user && current_user.admin
-            = link_to "##{@build.runner.id}", ci_admin_runner_path(@build.runner.id)
+            = link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id)
           - elsif @build.runner
             \##{@build.runner.id}
 
@@ -140,7 +142,7 @@
         %h4.title
           Commit
           .pull-right
-            %small 
+            %small
               = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
         %p
           %span.attr-name Branch:
@@ -162,7 +164,7 @@
 
       - if @builds.present?
         .build-widget
-          %h4.title #{pluralize(@builds.count(:id), "other build")} for 
+          %h4.title #{pluralize(@builds.count(:id), "other build")} for
           = succeed ":" do
             = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
           %table.table.builds
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index b277b765b6b526a0c123e01823c0e9cdd9012f5a..1f639fecc308decd4fb7bff135ec9c6be8ca2108 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -18,10 +18,11 @@
           = link_to new_namespace_project_snippet_path(@project.namespace, @project) do
             = icon('file-text-o fw')
             New snippet
+
       - if can?(current_user, :push_code, @project)
         %li.divider
         %li
-          = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'), title: 'New file' do
+          = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
             = icon('file fw')
             New file
         %li
@@ -32,3 +33,20 @@
           = link_to new_namespace_project_tag_path(@project.namespace, @project) do
             = icon('tags fw')
             New tag
+      - elsif current_user && current_user.already_forked?(@project)
+        %li.divider
+        %li
+          = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
+            = icon('file fw')
+            New file
+      - elsif can?(current_user, :fork_project, @project)
+        %li.divider
+        %li
+          - continue_params = { to:         namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
+                                notice:     edit_in_new_fork_notice,
+                                notice_now: edit_in_new_fork_notice_now }
+          - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key:  current_user.namespace.id,
+                                                                                  continue:       continue_params)
+          = link_to fork_path, method: :post do
+            = icon('file fw')
+            New file
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 2d3abf09051852a7bab10bf71923ce8726e86614..133531887a202cfabb18762643723d48d437c55f 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -4,10 +4,15 @@
       = 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')
         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')
+        Fork
+      %div.count-with-arrow
+        %span.arrow
         %span.count
           = @project.forks_count
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 41a3ec6d90fafbed9a389e5cd59486ad678c4837..21ba426aaa1e779a139aa29bc25ecc32be41c49f 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -1,19 +1,21 @@
 - if current_user
   = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has_tooltip', method: :post, remote: true, title: "Star project" do
-    = icon('star fw')
-    %span.count
+    - if current_user.starred?(@project)
+      = icon('star fw')
+      %span.starred Unstar
+    - else
+      = icon('star-o fw')
+      %span Star
+  %div.count-with-arrow
+    %span.arrow
+    %span.count.star-count
       = @project.star_count
 
-  :javascript
-    $('.project-home-panel .toggle-star').on('ajax:success', function (e, data, status, xhr) {
-      $(this).replaceWith(data.html);
-    })
-    .on('ajax:error', function (e, xhr, status, error) {
-      new Flash('Star toggle failed. Try again later.', 'alert');
-    });
-
 - else
   = link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do
     = icon('star fw')
+    Star
+  %div.count-with-arrow
+    %span.arrow
     %span.count
       = @project.star_count
diff --git a/app/views/projects/ci_services/_form.html.haml b/app/views/projects/ci_services/_form.html.haml
deleted file mode 100644
index 397832e56dbe8f7768578a17cde73f04a3251317..0000000000000000000000000000000000000000
--- a/app/views/projects/ci_services/_form.html.haml
+++ /dev/null
@@ -1,54 +0,0 @@
-%h3.page-title
-  = @service.title
-  = boolean_to_icon @service.activated?
-
-%p= @service.description
-
-
-%hr
-
-= form_for(@service, as: :service, url: namespace_project_ci_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
-  - if @service.errors.any?
-    .alert.alert-danger
-      %ul
-        - @service.errors.full_messages.each do |msg|
-          %li= msg
-
-  - if @service.help.present?
-    .bs-callout
-      = @service.help
-
-  .form-group
-    = f.label :active, "Active", class: "control-label"
-    .col-sm-10
-      = f.check_box :active
-
-  - @service.fields.each do |field|
-    - name = field[:name]
-    - label = field[:label] || name
-    - value = @service.send(name)
-    - type = field[:type]
-    - placeholder = field[:placeholder]
-    - choices = field[:choices]
-    - default_choice = field[:default_choice]
-    - help = field[:help]
-
-    .form-group
-      = f.label label, class: "control-label"
-      .col-sm-10
-        - if type == 'text'
-          = f.text_field name, class: "form-control", placeholder: placeholder
-        - elsif type == 'textarea'
-          = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder
-        - elsif type == 'checkbox'
-          = f.check_box name
-        - elsif type == 'select'
-          = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
-        - if help
-          .light #{help}
-
-  .form-actions
-    = f.submit 'Save', class: 'btn btn-save'
-    &nbsp;
-    - if @service.valid? && @service.activated? && @service.can_test?
-      = link_to 'Test settings', test_namespace_project_ci_service_path(@project.namespace, @project, @service.to_param), class: 'btn'
diff --git a/app/views/projects/ci_services/edit.html.haml b/app/views/projects/ci_services/edit.html.haml
deleted file mode 100644
index dacb6b4f6f4d159ade72c501d4bf0332a8019333..0000000000000000000000000000000000000000
--- a/app/views/projects/ci_services/edit.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-- page_title @service.title, "CI Services"
-= render 'form'
diff --git a/app/views/projects/ci_services/index.html.haml b/app/views/projects/ci_services/index.html.haml
deleted file mode 100644
index 3f26c7851d84d5f6d3b959bd630cfb573d1cb75f..0000000000000000000000000000000000000000
--- a/app/views/projects/ci_services/index.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-- page_title "CI Services"
-%h3.page-title Project services
-%p.light Project services allow you to integrate GitLab CI with other applications
-
-%table.table
-  %thead
-    %tr
-      %th
-      %th Service
-      %th Description
-      %th Last edit
-  - @services.sort_by(&:title).each do |service|
-    %tr
-      %td
-        = boolean_to_icon service.activated?
-      %td
-        = link_to edit_namespace_project_ci_service_path(@project.namespace, @project, service.to_param) do
-          %strong= service.title
-      %td
-        = service.description
-      %td.light
-        = time_ago_in_words service.updated_at
-        ago
diff --git a/app/views/projects/ci_settings/_form.html.haml b/app/views/projects/ci_settings/_form.html.haml
deleted file mode 100644
index ee6b8885e2def0781d600e8f3136749cc15bb4cc..0000000000000000000000000000000000000000
--- a/app/views/projects/ci_settings/_form.html.haml
+++ /dev/null
@@ -1,120 +0,0 @@
-%h3.page-title
-  CI settings
-%hr
-.bs-callout.help-callout
-  %p
-    If you want to test your .gitlab-ci.yml, you can use special tool - #{link_to "Lint", ci_lint_path}
-  %p
-    Edit your
-    #{link_to ".gitlab-ci.yml using web-editor", yaml_web_editor_link(@ci_project)}
-
-- unless @project.empty_repo?
-  %p
-    Paste build status image for #{@repository.root_ref} with next link
-    = link_to '#', class: 'badge-codes-toggle btn btn-default btn-xs' do
-      Status Badge
-    .badge-codes-block.bs-callout.bs-callout-info.hide
-      %p
-        Status badge for
-        %span.label.label-info #{@ref}
-        branch
-      %div
-        %label Markdown:
-        = text_field_tag 'badge_md', markdown_badge_code(@ci_project, @repository.root_ref), readonly: true, class: 'form-control'
-        %label Html:
-        = text_field_tag 'badge_html', html_badge_code(@ci_project, @repository.root_ref), readonly: true, class: 'form-control'
-
-= nested_form_for @ci_project, url: namespace_project_ci_settings_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
-  - if @ci_project.errors.any?
-    #error_explanation
-      %p.lead= "#{pluralize(@ci_project.errors.count, "error")} prohibited this project from being saved:"
-      .alert.alert-error
-        %ul
-          - @ci_project.errors.full_messages.each do |msg|
-            %li= msg
-
-  %fieldset
-    %legend Build settings
-    .form-group
-      = label_tag nil, class: 'control-label' do
-        Get code
-      .col-sm-10
-        %p Get recent application code using the following command:
-        .radio
-          = label_tag do
-            = f.radio_button :allow_git_fetch, 'false'
-            %strong git clone
-            .light Slower but makes sure you have a clean dir before every build
-        .radio
-          = label_tag do
-            = f.radio_button :allow_git_fetch, 'true'
-            %strong git fetch
-            .light Faster
-    .form-group
-      = f.label :timeout_in_minutes, 'Timeout', class: 'control-label'
-      .col-sm-10
-        = f.number_field :timeout_in_minutes, class: 'form-control', min: '0'
-        .light per build in minutes
-
-
-  %fieldset
-    %legend Build Schedule
-    .form-group
-      = f.label :always_build, 'Schedule build', class: 'control-label'
-      .col-sm-10
-        .checkbox
-          = f.label :always_build do
-            = f.check_box :always_build
-            %span.light Repeat last build after X hours if no builds
-    .form-group
-      = f.label :polling_interval, "Build interval", class: 'control-label'
-      .col-sm-10
-        = f.number_field :polling_interval, placeholder: '5', min: '0', class: 'form-control'
-        .light In hours
-
-  %fieldset
-    %legend Project settings
-    .form-group
-      = f.label :default_ref, "Make tabs for the following branches", class: 'control-label'
-      .col-sm-10
-        = f.text_field :default_ref, class: 'form-control', placeholder: 'master, stable'
-        .light You will be able to filter builds by the following branches
-    .form-group
-      = f.label :public, 'Public mode', class: 'control-label'
-      .col-sm-10
-        .checkbox
-          = f.label :public do
-            = f.check_box :public
-            %span.light Anyone can see project and builds
-    .form-group
-      = f.label :coverage_regex, "Test coverage parsing", class: 'control-label'
-      .col-sm-10
-        .input-group
-          %span.input-group-addon /
-          = f.text_field :coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
-          %span.input-group-addon /
-        .light 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+\%
-
-  %fieldset
-    %legend Advanced settings
-    .form-group
-      = f.label :token, "CI token", class: 'control-label'
-      .col-sm-10
-        = f.text_field :token, class: 'form-control', placeholder: 'xEeFCaDAB89'
-
-  .form-actions
-    = f.submit 'Save changes', class: 'btn btn-save'
-    - unless @ci_project.new_record?
-      = link_to 'Remove Project', ci_project_path(@ci_project), method: :delete, data: { confirm: 'Project will be removed. Are you sure?' }, class: 'btn btn-danger pull-right'
diff --git a/app/views/projects/ci_settings/_no_runners.html.haml b/app/views/projects/ci_settings/_no_runners.html.haml
deleted file mode 100644
index 1374e6680f9506e93eccc673ec976d797ea3c024..0000000000000000000000000000000000000000
--- a/app/views/projects/ci_settings/_no_runners.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-.alert.alert-danger
-  %p
-    There are NO runners to build this project.
-    %br
-    You can add Specific runner for this project on Runners page
-
-    - if current_user.admin
-      or add Shared runner for whole application in admin area.
diff --git a/app/views/projects/ci_settings/edit.html.haml b/app/views/projects/ci_settings/edit.html.haml
deleted file mode 100644
index acc912d459606c5831bf20246ad1f8c2eaeefe2a..0000000000000000000000000000000000000000
--- a/app/views/projects/ci_settings/edit.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-- page_title "CI Settings"
-
-- if no_runners_for_project?(@ci_project)
-  = render 'no_runners'
-
-= render 'form'
diff --git a/app/views/projects/ci_web_hooks/index.html.haml b/app/views/projects/ci_web_hooks/index.html.haml
deleted file mode 100644
index 2998fb08ff1a5678cd99fcd0ece1f3e5427857de..0000000000000000000000000000000000000000
--- a/app/views/projects/ci_web_hooks/index.html.haml
+++ /dev/null
@@ -1,94 +0,0 @@
-- page_title "CI Web Hooks"
-%h3.page-title
-  CI Web hooks
-
-%p.light
-  Web Hooks can be used for binding events when build completed.
-
-%hr.clearfix
-
-= form_for @web_hook, url: namespace_project_ci_web_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
-  -if @web_hook.errors.any?
-    .alert.alert-danger
-      - @web_hook.errors.full_messages.each do |msg|
-        %p= msg
-  .form-group
-    = f.label :url, "URL", class: 'control-label'
-    .col-sm-10
-      = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json'
-  .form-actions
-    = f.submit "Add Web Hook", class: "btn btn-create"
-
--if @web_hooks.any?
-  %h4 Activated web hooks (#{@web_hooks.count})
-  .table-holder
-    %table.table
-      - @web_hooks.each do |hook|
-        %tr
-          %td
-            .clearfix
-              %span.monospace= hook.url
-          %td
-            .pull-right
-              - if @ci_project.commits.any?
-                = link_to 'Test Hook', test_namespace_project_ci_web_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
-              = link_to 'Remove', namespace_project_ci_web_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
-
-%h4 Web Hook data example
-
-:erb
-  <pre>
-    <code>
-      {
-        "build_id": 2,
-        "build_name":"rspec_linux"
-        "build_status": "failed",
-        "build_started_at": "2014-05-05T18:01:02.563Z",
-        "build_finished_at": "2014-05-05T18:01:07.611Z",
-        "project_id": 1,
-        "project_name": "Brightbox \/ Brightbox Cli",
-        "gitlab_url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli",
-        "ref": "master",
-        "sha": "a26cf5de9ed9827746d4970872376b10d9325f40",
-        "before_sha": "34f57f6ba3ed0c21c5e361bbb041c3591411176c",
-        "push_data": {
-          "before": "34f57f6ba3ed0c21c5e361bbb041c3591411176c",
-          "after": "a26cf5de9ed9827746d4970872376b10d9325f40",
-          "ref": "refs\/heads\/master",
-          "user_id": 1,
-          "user_name": "Administrator",
-          "project_id": 5,
-          "repository": {
-            "name": "Brightbox Cli",
-            "url": "dzaporozhets@localhost:brightbox\/brightbox-cli.git",
-            "description": "Voluptatibus quae error consectetur voluptas dolores vel excepturi possimus.",
-            "homepage": "http:\/\/localhost:3000\/brightbox\/brightbox-cli"
-          },
-          "commits": [
-            {
-              "id": "a26cf5de9ed9827746d4970872376b10d9325f40",
-              "message": "Release v1.2.2",
-              "timestamp": "2014-04-22T16:46:42+03:00",
-              "url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli\/commit\/a26cf5de9ed9827746d4970872376b10d9325f40",
-              "author": {
-                "name": "Paul Thornthwaite",
-                "email": "tokengeek@gmail.com"
-              }
-            },
-            {
-              "id": "34f57f6ba3ed0c21c5e361bbb041c3591411176c",
-              "message": "Fix server user data update\n\nIncorrect condition was being used so Base64 encoding option was having\nopposite effect from desired.",
-              "timestamp": "2014-04-11T18:17:26+03:00",
-              "url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli\/commit\/34f57f6ba3ed0c21c5e361bbb041c3591411176c",
-              "author": {
-                "name": "Paul Thornthwaite",
-                "email": "tokengeek@gmail.com"
-              }
-            }
-          ],
-          "total_commits_count": 2,
-          "ci_yaml_file":"rspec_linux:\r\n  script: ls\r\n"
-        }
-      }
-    </code>
-  </pre>
diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..329aaa0bb8b77bfd4ee9dbb76aceccd872db2776
--- /dev/null
+++ b/app/views/projects/commit/_builds.html.haml
@@ -0,0 +1,68 @@
+.gray-content-block.middle-block
+  .pull-right
+    - if can?(current_user, :manage_builds, @ci_commit.project)
+      - if @ci_commit.builds.latest.failed.any?(&:retryable?)
+        = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
+
+      - if @ci_commit.builds.running_or_pending.any?
+        = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
+
+  .oneline
+    = pluralize @statuses.count(:id), "build"
+    - if defined?(link_to_commit) && link_to_commit
+      for commit
+      = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: "monospace"
+    - if @ci_commit.duration > 0
+      in
+      = time_interval_in_words @ci_commit.duration
+
+- if @ci_commit.yaml_errors.present?
+  .bs-callout.bs-callout-danger
+    %h4 Found errors in your .gitlab-ci.yml:
+    %ul
+      - @ci_commit.yaml_errors.split(",").each do |error|
+        %li= error
+    You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
+
+- if @ci_commit.project.builds_enabled? && !@ci_commit.ci_yaml_file
+  .bs-callout.bs-callout-warning
+    \.gitlab-ci.yml not found in this commit
+
+.table-holder
+  %table.table.builds
+    %thead
+      %tr
+        %th Status
+        %th Build ID
+        %th Ref
+        %th Stage
+        %th Name
+        %th Duration
+        %th Finished at
+        - if @ci_commit.project.build_coverage_enabled?
+          %th Coverage
+        %th
+    - @ci_commit.refs.each do |ref|
+      = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered,
+               locals: { coverage: @ci_commit.project.build_coverage_enabled?, stage: true, allow_retry: true }
+
+- if @ci_commit.retried.any?
+  .gray-content-block.second-block
+    Retried builds
+
+  .table-holder
+    %table.table.builds
+      %thead
+        %tr
+          %th Status
+          %th Build ID
+          %th Ref
+          %th Stage
+          %th Name
+          %th Duration
+          %th Finished at
+          - if @ci_commit.project.build_coverage_enabled?
+            %th Coverage
+          %th
+      = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried,
+               locals: { coverage: @ci_commit.project.build_coverage_enabled?, stage: true }
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
index c73ba74f5efc3a1c15ce09492cd7a40c42d86385..f74f8b427ecaac0401fd239b7638abac1f57f056 100644
--- a/app/views/projects/commit/_ci_menu.html.haml
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -1,4 +1,4 @@
-%ul.center-top-menu.commit-ci-menu
+%ul.center-top-menu.no-top.no-bottom.commit-ci-menu
   = nav_link(path: 'commit#show') do
     = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do
       Changes
@@ -6,4 +6,4 @@
   = nav_link(path: 'commit#builds') do
     = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
       Builds
-      %span.badge= @builds.count(:id)
+      %span.badge= @statuses.count
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 776768537d0ac9efb4efa6ff93fc2c9e7c890209..ddb77fd796b13f03c74cc7e9aea68e8e0bc46e37 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -13,13 +13,15 @@
         - unless @commit.parents.length > 1
           %li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch)
         %li= link_to "Plain Diff",    namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff)
-    = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-primary btn-grouped" do
-      %span Browse Code »
+    = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-grouped" do
+      = icon('files-o')
+      Browse Files
   %div
 
 %p
   %span.light Commit
   = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
+  = clipboard_button(clipboard_text: @commit.id)
 .commit-info-row
   %span.light Authored by
   %strong
@@ -43,17 +45,17 @@
     = link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
       = ci_status_icon(@ci_commit)
       build:
-      = @ci_commit.status
+      = ci_status_label(@ci_commit)
 
 .commit-info-row.branches
   %i.fa.fa-spinner.fa-spin
 
 .commit-box.gray-content-block.middle-block
   %h3.commit-title
-    = gfm escape_once(@commit.title)
+    = markdown escape_once(@commit.title), pipeline: :single_line
   - if @commit.description.present?
     %pre.commit-description
-      = preserve(gfm(escape_once(@commit.description)))
+      = preserve(markdown(escape_once(@commit.description), pipeline: :single_line))
 
 :javascript
   $(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml
index 00cf9c761025df9d9aee80c697f969e27510a6ca..99d62503a949ce7e8634532f9b58a3fa9d1a67b0 100644
--- a/app/views/projects/commit/builds.html.haml
+++ b/app/views/projects/commit/builds.html.haml
@@ -3,70 +3,4 @@
 = render "commit_box"
 = render "ci_menu"
 
-
-- if @ci_commit.yaml_errors.present?
-  .bs-callout.bs-callout-danger
-    %h4 Found errors in your .gitlab-ci.yml:
-    %ul
-      - @ci_commit.yaml_errors.split(",").each do |error|
-        %li= error
-
-- unless @ci_commit.ci_yaml_file
-  .bs-callout.bs-callout-warning
-    \.gitlab-ci.yml not found in this commit
-
-.gray-content-block.second-block
-  Latest builds
-
-  .pull-right
-    - if @ci_commit.duration > 0
-      %i.fa.fa-time
-      #{time_interval_in_words @ci_commit.duration}
-
-    &nbsp;
-
-    - if @ci_project && current_user && can?(current_user, :manage_builds, @project)
-      - if @ci_commit.builds.latest.failed.any?(&:retryable?)
-        = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-primary', method: :post
-
-      - if @ci_commit.builds.running_or_pending.any?
-        = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-danger', method: :post
-
-.table-holder
-  %table.table.builds
-    %thead
-      %tr
-        %th Status
-        %th Build ID
-        %th Ref
-        %th Stage
-        %th Name
-        %th Duration
-        %th Finished at
-        - if @ci_project && @ci_project.coverage_enabled?
-          %th Coverage
-        %th
-    - @ci_commit.refs.each do |ref|
-      = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered,
-               locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true, allow_retry: true }
-
-- if @ci_commit.retried.any?
-  .gray-content-block.second-block
-    Retried builds
-
-  .table-holder
-    %table.table.builds
-      %thead
-        %tr
-          %th Status
-          %th Build ID
-          %th Ref
-          %th Stage
-          %th Name
-          %th Duration
-          %th Finished at
-          - if @ci_project && @ci_project.coverage_enabled?
-            %th Coverage
-          %th
-      = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried,
-               locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true }
+= render "builds"
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 85e203cbe574f07e49211f0b925cfda1551564e5..069b8b1f1696b897f4b36fd72e7d0109fdefc4f6 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,6 +1,9 @@
 - page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
 = render "projects/commits/header_title"
 = render "commit_box"
-= render "ci_menu" if @ci_commit
+- if @ci_commit
+  = render "ci_menu"
+- else
+  %div.block-connector
 = render "projects/diffs/diffs", diffs: @diffs, project: @project
 = render "projects/notes/notes_with_form"
diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml
index 9a0e7bff3f1ebc805551909e1fe04cee147fb254..74a05df24d3db44b5365de2cc6f562325156cd12 100644
--- a/app/views/projects/commit_statuses/_commit_status.html.haml
+++ b/app/views/projects/commit_statuses/_commit_status.html.haml
@@ -1,24 +1,29 @@
 %tr.commit_status
   %td.status
-    = ci_status_with_icon(commit_status.status)
+    - if commit_status.target_url
+      = link_to commit_status.target_url, class: "ci-status ci-#{commit_status.status}" do
+        = ci_icon_for_status(commit_status.status)
+        = commit_status.status
+    - else
+      = ci_status_with_icon(commit_status.status)
 
   %td.commit_status-link
     - if commit_status.target_url
       = link_to commit_status.target_url do
-        %strong Build ##{commit_status.id}
+        %strong ##{commit_status.id}
     - else
-      %strong Build ##{commit_status.id}
+      %strong ##{commit_status.id}
 
     - if commit_status.show_warning?
       %i.fa.fa-warning.text-warning
 
   - if defined?(commit_sha) && commit_sha
     %td
-      = link_to commit_status.short_sha, namespace_project_commit_path(@project.namespace, @project, commit_status.sha), class: "monospace"
-
+      = link_to commit_status.short_sha, namespace_project_commit_path(commit_status.project.namespace, commit_status.project, commit_status.sha), class: "monospace"
+      
   %td
     - if commit_status.ref
-      = link_to commit_status.ref, namespace_project_commits_path(@project.namespace, @project, commit_status.ref)
+      = link_to commit_status.ref, namespace_project_commits_path(commit_status.project.namespace, commit_status.project, commit_status.ref)
     - else
       .light none
 
@@ -61,10 +66,10 @@
 
   %td
     .pull-right
-      - if current_user && can?(current_user, :download_build_artifacts, @project) && commit_status.download_url
+      - if current_user && can?(current_user, :download_build_artifacts, commit_status.project) && commit_status.download_url
         = link_to commit_status.download_url, title: 'Download artifacts' do
           %i.fa.fa-download
-      - if current_user && can?(current_user, :manage_builds, commit_status.gl_project)
+      - if current_user && can?(current_user, :manage_builds, commit_status.project)
         - if commit_status.active?
           - if commit_status.cancel_url
             = link_to commit_status.cancel_url, method: :post, title: 'Cancel' do
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 805be332e64510002ec7d9f7a754fe4a0a78387c..28b82dd31f3eb549d1eb7111f0c4d501b09efb5f 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -9,7 +9,7 @@
 - cache_key.push(ci_commit.status) if ci_commit
 
 = cache(cache_key) do
-  %li.commit.js-toggle-container
+  %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
     .commit-row-title
       %strong.str-truncated
         = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
@@ -20,8 +20,8 @@
         - if ci_commit
           = render_ci_status(ci_commit)
           &nbsp;
-        = clipboard_button
-        = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id", data: {clipboard_text: commit.id}
+        = clipboard_button(clipboard_text: commit.id)
+        = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
 
       .notes_count
         - if note_count > 0
@@ -32,7 +32,7 @@
     - if commit.description?
       .commit-row-description.js-toggle-content
         %pre
-          = preserve(gfm(escape_once(commit.description)))
+          = preserve(markdown(escape_once(commit.description), pipeline: :single_line))
 
     .commit-row-info
       = commit_author_link(commit, avatar: true, size: 24)
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index f11a41cfd7bdbe5040e9dba75ff5d54b9c8f1777..fcccb002d7e8d482f5358ae34bfc459304fae546 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -3,6 +3,11 @@
     = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
       Commits
       %span.badge= number_with_delimiter(@repository.commit_count)
+
+  = nav_link(controller: %w(network)) do
+    = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
+      Network
+
   = nav_link(controller: :compare) do
     = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
       Compare
diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index 268b9b815ee89b8197abec3a523df41c7c6f6619..7ffa731719607462872c9645a337b563708de995 100644
--- a/app/views/projects/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
@@ -17,7 +17,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
         xml.name commit.author_name
         xml.email commit.author_email
       end
-      xml.summary gfm(commit.description)
+      xml.summary markdown(commit.description, pipeline: :single_line)
     end
   end
 end
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 39755efd2fda2e9b326a7f6a4eb3a3026d1be333..51088a7dea89b0e97622dde7a1b0ba641ea8c0d8 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -7,11 +7,11 @@
   = render "form"
 
 - if @commits.present?
-  .prepend-top-20
+  .prepend-top-default
     = render "projects/commits/commit_list"
     = render "projects/diffs/diffs", diffs: @diffs, project: @project
 - else
-  .light-well.prepend-top-20
+  .light-well.prepend-top-default
     .center
       %h4
         There isn't anything to compare.
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index 91675b3738ecb9f2d734c2dacd415713816ea5e9..5e182af26693f87f24cbefc167a8e3d464b7ba03 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -1,5 +1,5 @@
 %div
-  = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal' } do |f|
+  = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f|
     -if @key.errors.any?
       .alert.alert-danger
         %ul
@@ -8,16 +8,15 @@
 
     .form-group
       = f.label :title, class: "control-label"
-      .col-sm-10= f.text_field :title, class: 'form-control'
+      .col-sm-10= f.text_field :title, class: 'form-control', autofocus: true, required: true
     .form-group
       = f.label :key, class: "control-label"
       .col-sm-10
         %p.light
           Paste a machine public key here. Read more about how to generate it
           = link_to "here", help_page_path("ssh", "README")
-        = f.text_area :key, class: "form-control thin_area", rows: 5
+        = f.text_area :key, class: "form-control thin_area", rows: 5, required: true
 
     .form-actions
-      = f.submit 'Create', class: "btn-create btn"
+      = f.submit 'Create Deploy Key', class: "btn-create btn"
       = link_to "Cancel", namespace_project_deploy_keys_path(@project.namespace, @project), class: "btn btn-cancel"
-
diff --git a/app/views/projects/deploy_keys/new.html.haml b/app/views/projects/deploy_keys/new.html.haml
index 01c810aee180b24f580f78f1833b4606590e9e1b..01fab3008a704c7af51dda06eb2b6c420f6df371 100644
--- a/app/views/projects/deploy_keys/new.html.haml
+++ b/app/views/projects/deploy_keys/new.html.haml
@@ -1,5 +1,5 @@
 - page_title "New Deploy Key"
-%h3.page-title New Deploy key
+%h3.page-title New Deploy Key
 %hr
 
 = render 'form'
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 416fb4da071e204fa302c2bcfc4609490ec658fe..f9d661d59d291b83a15d7ed2a43cfb1f0bce868d 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -3,7 +3,7 @@
 
 - diff_files = safe_diff_files(diffs)
 
-.gray-content-block.second-block.oneline-block
+.gray-content-block.middle-block.oneline-block
   .inline-parallel-buttons
     .btn-group
       = inline_diff_btn
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index c745b4e69bfb0b8cf4b053e32d9da2b0fe8e0a95..517f6aef7c5a89e356407058e781455467cb8640 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -2,22 +2,29 @@
   .diff-header{id: "file-path-#{hexdigest(diff_file.file_path)}"}
     - if diff_file.diff.submodule?
       %span
-        - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
-        = submodule_link(submodule_item, @commit.id, project.repository)
+        = icon('archive fw')
+        %strong
+          = submodule_link(blob, @commit.id, project.repository)
     - else
       %span
+        = blob_icon blob.mode, blob.name
+        = link_to "#diff-#{i}" do
+          %strong
+            = diff_file.new_path
+
         - if diff_file.deleted_file
-          = "#{diff_file.old_path} deleted"
+          deleted
         - elsif diff_file.renamed_file
-          = "#{diff_file.old_path} renamed to #{diff_file.new_path}"
-        - else
-          = diff_file.new_path
+          renamed from
+          %strong
+            = diff_file.old_path
 
         - if diff_file.mode_changed?
-          %span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
+          %small
+            = "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
 
       .diff-controls
-        - if blob.text?
+        - if blob_text_viewable?(blob)
           = link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do
             %i.fa.fa-comments
           &nbsp;
@@ -25,14 +32,15 @@
         - if editable_diff?(diff_file)
           = edit_blob_link(@merge_request.source_project,
               @merge_request.source_branch, diff_file.new_path,
-              after: '&nbsp;', from_merge_request_id: @merge_request.id)
+              from_merge_request_id: @merge_request.id)
+          &nbsp;
 
         = view_file_btn(diff_commit.id, diff_file, project)
 
   .diff-content.diff-wrap-lines
     -# Skipp all non non-supported blobs
     - return unless blob.respond_to?('text?')
-    - if blob.text?
+    - if blob_text_viewable?(blob)
       - if diff_view == 'parallel'
         = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
       - else
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 3ebc175648ee985eadf314f9f16edcc41d29541b..650629ef1b91af8cb1d1cfbc59556c27d327b603 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -14,7 +14,7 @@
               = f.label :name, class: 'control-label' do
                 Project name
               .col-sm-10
-                = f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit"
+                = f.text_field :name, class: "form-control", id: "project_name_edit"
 
 
             .form-group
@@ -22,12 +22,12 @@
                 Project description
                 %span.light (optional)
               .col-sm-10
-                = f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250
+                = f.text_area :description, class: "form-control", rows: 3, maxlength: 250
 
-            - if @project.repository.exists? && @project.repository.branch_names.any?
+            - unless @project.empty_repo?
               .form-group
                 = f.label :default_branch, "Default Branch", class: 'control-label'
-                .col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'})
+                .col-sm-10= f.select(:default_branch, @project.repository.branch_names, {}, {class: 'select2 select-wide'})
 
 
           = render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can_change_visibility_level?(@project, current_user), form_model: @project
@@ -35,7 +35,7 @@
           .form-group
             = f.label :tag_list, "Tags", class: 'control-label'
             .col-sm-10
-              = f.text_field :tag_list, maxlength: 2000, class: "form-control"
+              = f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
               %p.help-block Separate tags with commas.
 
           %fieldset.features
@@ -112,6 +112,62 @@
                   %hr
                   = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
 
+          %fieldset.features
+            %legend
+              Continuous Integration
+            .form-group
+              .col-sm-offset-2.col-sm-10
+                %p Get recent application code using the following command:
+                .radio
+                  = f.label :build_allow_git_fetch 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 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: 'control-label'
+              .col-sm-10
+                = 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: 'control-label'
+              .col-sm-10
+                .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+\%
+
+
+          %fieldset.features
+            %legend
+              Advanced settings
+            .form-group
+              = f.label :runners_token, "CI token", class: 'control-label'
+              .col-sm-10
+                = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
+                %p.help-block The secure token used to checkout project.
+
           .form-actions
             = f.submit 'Save changes', class: "btn btn-save"
 
@@ -130,9 +186,11 @@
                 The project can be committed to.
                 %br
                 %strong Once active this project shows up in the search and on the dashboard.
-              = link_to 'Unarchive', unarchive_namespace_project_path(@project.namespace, @project),
-                  data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
-                  method: :post, class: "btn btn-success"
+
+              .form-actions
+                = link_to 'Unarchive project', unarchive_namespace_project_path(@project.namespace, @project),
+                    data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
+                    method: :post, class: "btn btn-success"
         - else
           .panel.panel-warning
             .panel-heading
@@ -144,9 +202,11 @@
                 It is hidden from the dashboard and doesn't show up in searches.
                 %br
                 %strong Archived projects cannot be committed to!
-              = link_to 'Archive', archive_namespace_project_path(@project.namespace, @project),
-                  data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
-                  method: :post, class: "btn btn-warning"
+
+              .form-actions
+                = link_to 'Archive project', archive_namespace_project_path(@project.namespace, @project),
+                    data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
+                    method: :post, class: "btn btn-warning"
       - else
         .nothing-here-block Only the project owner can archive a project
 
@@ -160,7 +220,7 @@
                 Project name
               .col-sm-9
                 .form-group
-                  = f.text_field :name, placeholder: "Example Project", class: "form-control"
+                  = f.text_field :name, class: "form-control"
             .form-group
               = f.label :path, class: 'control-label' do
                 %span Path
@@ -170,12 +230,11 @@
                     .input-group-addon
                       #{URI.join(root_url, @project.namespace.path)}/
                     = f.text_field :path, class: 'form-control'
-                    %span.input-group-addon .git
                 %ul
                   %li Be careful. Renaming a project's repository can have unintended side effects.
                   %li You will need to update your local repositories to point to the new location.
             .form-actions
-              = f.submit 'Rename', class: "btn btn-warning"
+              = f.submit 'Rename project', class: "btn btn-warning"
 
       - if can?(current_user, :change_namespace, @project)
         .panel.panel-default.panel.panel-danger
@@ -194,7 +253,7 @@
                     %li You can only transfer the project to namespaces you manage.
                     %li You will need to update your local repositories to point to the new location.
               .form-actions
-                = f.submit 'Transfer', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
+                = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
       - else
         .nothing-here-block Only the project owner can transfer a project
 
@@ -209,7 +268,8 @@
                   #{link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)}.
                   %br
                   %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
-                = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
+                .form-actions
+                  = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
         - else
           .nothing-here-block Only the project owner can remove the fork relationship.
 
@@ -222,8 +282,8 @@
                 Removing the project will delete its repository and all related resources including issues, merge requests etc.
                 %br
                 %strong Removed projects cannot be restored!
-
-              = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
+              .form-actions
+                = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
       - else
         .nothing-here-block Only the project owner can remove a project.
 
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index c3858e78caddf4e44fc1e79a837bad28b4029e33..503d156661eac9df3bfef0d021f1d2629072c2d5 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,21 +1,20 @@
-.alert_holder
+= content_for :flash_message do
   - if current_user && can?(current_user, :download_code, @project)
     = render 'shared/no_ssh'
     = render 'shared/no_password'
 
 = render "home_panel"
 
-.gray-content-block.center
+.gray-content-block.second-block.center
   %h3.page-title
     The repository for this project is empty
-  - if can?(current_user, :download_code, @project)
+  - if can?(current_user, :push_code, @project)
     %p
       If you already have files you can push them using command line instructions below.
-      %br
-      - if can?(current_user, :push_code, @project)
-        Otherwise you can start with
-        = link_to "adding README", new_readme_path, class: 'underlined-link'
-        file to this project.
+    %p
+      Otherwise you can start with
+      = link_to "adding README", new_readme_path, class: 'underlined-link'
+      file to this project.
 
 - if can?(current_user, :download_code, @project)
   .prepend-top-20
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index f0b0a11c04a30de76383dd410d22bac157c3d580..8a2c027a45517d6ad288122d5b416c840f9ac006 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -43,4 +43,3 @@
       %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/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index 03d0733f913268357e05805f9fc0ebba6b76aef7..a47643bd09c642add4a0abca355b2253b0b9881f 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -3,6 +3,8 @@
     = link_to 'Contributors', namespace_project_graph_path
   = nav_link(action: :commits) do
     = link_to 'Commits', commits_namespace_project_graph_path
+  = nav_link(action: :languages) do
+    = link_to 'Languages', languages_namespace_project_graph_path
   - if @project.builds_enabled?
     = nav_link(action: :ci) do
       = link_to ci_namespace_project_graph_path do
diff --git a/app/views/projects/graphs/ci/_overall.haml b/app/views/projects/graphs/ci/_overall.haml
index cf4285a2671dc0d505834e5294f0471f6c040533..4b12e5f2da1778773dd2a168a20159f9a39c36bd 100644
--- a/app/views/projects/graphs/ci/_overall.haml
+++ b/app/views/projects/graphs/ci/_overall.haml
@@ -1,20 +1,19 @@
-- ci_project = @project.gitlab_ci_project
 %h4 Overall stats
 %ul
   %li
     Total:
-    %strong= pluralize ci_project.builds.count(:all), 'build'
+    %strong= pluralize @project.builds.count(:all), 'build'
   %li
     Successful:
-    %strong= pluralize ci_project.builds.success.count(:all), 'build'
+    %strong= pluralize @project.builds.success.count(:all), 'build'
   %li
     Failed:
-    %strong= pluralize ci_project.builds.failed.count(:all), 'build'
+    %strong= pluralize @project.builds.failed.count(:all), 'build'
   %li
     Success ratio:
     %strong
-      #{success_ratio(ci_project.builds.success, ci_project.builds.failed)}%
+      #{success_ratio(@project.builds.success, @project.builds.failed)}%
   %li
     Commits covered:
     %strong
-      = ci_project.commits.count(:all)
+      = @project.ci_commits.count(:all)
diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a7fab5b6d72b3122f288352fd5a78f6baa7ed398
--- /dev/null
+++ b/app/views/projects/graphs/languages.html.haml
@@ -0,0 +1,32 @@
+- page_title "Languages", "Graphs"
+= render "header_title"
+= render 'head'
+
+.gray-content-block.append-bottom-default
+  .oneline
+    Programming languages used in this repository
+
+.row
+  .col-md-8
+    %canvas#languages-chart{ height: 400 }
+  .col-md-4
+    %ul.bordered-list
+      - @languages.each do |language|
+        %li
+          %span{ style: "color: #{language[:color]}" }
+            = icon('circle')
+          &nbsp;
+          = language[:label]
+          .pull-right
+            = language[:value]
+            \%
+
+:javascript
+  var data = #{@languages.to_json};
+  var ctx = $("#languages-chart").get(0).getContext("2d");
+  var options = {
+    scaleOverlay: true,
+    responsive: true,
+    maintainAspectRatio: false
+  }
+  var myPieChart = new Chart(ctx).Pie(data, options);
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index 3702aeaecba0c396c8ab43d4968cc9cf6e249d95..b18d9197d0b2a025af0f728d6187301efb81e4ce 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -55,6 +55,13 @@
             %strong Merge Request events
           %p.light
             This url will be triggered when a merge request is created
+      %div
+        = f.check_box :build_events, class: 'pull-left'
+        .prepend-left-20
+          = f.label :build_events, class: 'list-label' do
+            %strong Build events
+          %p.light
+            This url will be triggered when the build status changes
   .form-group
     = f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
     .col-sm-10
@@ -78,7 +85,7 @@
           .clearfix
             %span.monospace= hook.url
           %p
-            - %w(push_events tag_push_events issues_events note_events merge_requests_events).each do |trigger|
+            - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
               - if hook.send(trigger)
                 %span.label.label-gray= trigger.titleize
             SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
diff --git a/app/views/projects/issues/_closed_by_box.html.haml b/app/views/projects/issues/_closed_by_box.html.haml
index aef352029d06d5425d2bee39b108af321c49dfb5..de415ae51a41d1979f408fc8102716dfd2c98f06 100644
--- a/app/views/projects/issues/_closed_by_box.html.haml
+++ b/app/views/projects/issues/_closed_by_box.html.haml
@@ -1,3 +1,2 @@
-.issue-closed-by-widget
-  = icon('check')
-  This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests.sort))} is accepted.
+.issue-closed-by-widget.gray-content-block.second-block.white
+  This issue will be closed automatically when merge request #{markdown(merge_requests_sentence(@closed_by_merge_requests), pipeline: :gfm)} is accepted.
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 020952dd0016978a95fd8af1c0bfa3b26da2667b..dc434cf38c4ab006432903456cfd5dace77a4865 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -1,38 +1,9 @@
 - content_for :note_actions do
   - if can?(current_user, :update_issue, @issue)
     - if @issue.closed?
-      = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue'
+      = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue'
     - else
-      = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close js-note-target-close', title: 'Close Issue'
+      = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-close js-note-target-close', title: 'Close Issue'
 
-= render 'shared/show_aside'
-
-.gray-content-block.second-block.oneline-block
-  .row
-    .col-md-9
-      .votes-holder.pull-right
-        #votes= render 'votes/votes_block', votable: @issue
-      .participants
-        %span= pluralize(@participants.count, 'participant')
-        - @participants.each do |participant|
-          = link_to_member(@project, participant, name: false, size: 24)
-    .col-md-3
-      .input-group.cross-project-reference
-        %span.slead.has_tooltip{title: 'Cross-project reference'}
-          = cross_project_reference(@project, @issue)
-        = clipboard_button
-
-.row
-  %section.col-md-9
-    .voting_notes#notes= render 'projects/notes/notes_with_form'
-  %aside.col-md-3
-    .issuable-affix
-      .context
-        = render 'shared/issuable/context', issuable: @issue
-
-      - if @issue.labels.any?
-        .issuable-context-title
-          %label Labels
-        .issue-show-labels
-          - @issue.labels.each do |label|
-            = link_to_label(label)
+#notes
+  = render 'projects/notes/notes_with_form'
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index f39bb7d2574a5aebc3f527e7d48ac1e2bb18e388..6588d9bdbe17522ed96524538ce9bf8ec386c568 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,9 +1,5 @@
-%div.issue-form-holder
-  %h3.page-title= @issue.new_record? ? "Create Issue" : "Edit Issue ##{@issue.iid}"
-  %hr
-
-  = form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f|
-    = render 'shared/issuable/form', f: f, issuable: @issue
+= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-requires-input' } do |f|
+  = render 'shared/issuable/form', f: f, issuable: @issue
 
 :javascript
   $('.assign-to-me-link').on('click', function(e){
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index d7657ee7e4066dd9dd673d5daa0eacbaa4dc2d79..f9cf4910df3e11c7f5ed55c32c158c9d28168e85 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -6,35 +6,42 @@
   .issue-title
     %span.issue-title-text
       = link_to_gfm issue.title, issue_path(issue), class: "row_title"
-    .issue-labels
-      - issue.labels.each do |label|
-        = link_to_label(label, project: issue.project)
-    .pull-right.light
+    %ul.controls.light
       - if issue.closed?
-        %span
+        %li
           CLOSED
+
       - if issue.assignee
-        = link_to_member(@project, issue.assignee, name: false)
+        %li
+          = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
+
       - note_count = issue.notes.user.count
       - if note_count > 0
-        &nbsp;
-        %span
-          %i.fa.fa-comments
-          = note_count
+        %li
+          = link_to issue_path(issue) + "#notes" do
+            = icon('comments')
+            = note_count
       - else
-        &nbsp;
-        %span.issue-no-comments
-          %i.fa.fa-comments
-          = 0
+        %li
+          = link_to issue_path(issue) + "#notes", class: "issue-no-comments" do
+            = icon('comments')
+            = note_count
 
   .issue-info
-    = "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
+    #{issue.to_reference} &middot;
+    opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
+    by #{link_to_member(@project, issue.author, avatar: false)}
     - if issue.milestone
       &nbsp;
-      %span
-        %i.fa.fa-clock-o
+      = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
+        = icon('clock-o')
         = issue.milestone.title
+    - if issue.labels.any?
+      &nbsp;
+      - issue.labels.each do |label|
+        = link_to_label(label, project: issue.project)
     - if issue.tasks?
+      &nbsp;
       %span.task-status
         = issue.task_status
 
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..254968e4f678f7850a25b3c74d33a77dd8b90531
--- /dev/null
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -0,0 +1,26 @@
+-if @merge_requests.any?
+  %h2.merge-requests-title
+    = pluralize(@merge_requests.count, 'Related Merge Request')
+  %ul.bordered-list
+    - has_any_ci = @merge_requests.any?(&:ci_commit)
+    - @merge_requests.each do |merge_request|
+      %li
+        %span.merge-request-ci-status
+          - if merge_request.ci_commit
+            = render_ci_status(merge_request.ci_commit)
+          - elsif has_any_ci
+            = icon('blank fw')
+        %span.merge-request-id
+          \##{merge_request.iid}
+        %span.merge-request-info
+          %strong
+            = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
+          - unless @issue.project.id == merge_request.target_project.id
+            in
+            - project = merge_request.target_project
+            = link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
+        %span.merge-request-status.prepend-left-10
+          - if merge_request.merged?
+            MERGED
+          - elsif merge_request.closed?
+            CLOSED
diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml
index 53b6f0879c9c568b87be742dd59e869714ec285c..20216297d2558fd57933a9aefd006e68e621a3e2 100644
--- a/app/views/projects/issues/edit.html.haml
+++ b/app/views/projects/issues/edit.html.haml
@@ -1,2 +1,8 @@
 - page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues"
+= render "header_title"
+
+%h3.page-title
+  Edit Issue ##{@issue.iid}
+%hr
+
 = render "form"
diff --git a/app/views/projects/issues/new.html.haml b/app/views/projects/issues/new.html.haml
index 153447baa1bcce667f4cd5ee34aa97397f8d03a5..b317a0c1cf4429721905ab89b4934fcdc0c8cb22 100644
--- a/app/views/projects/issues/new.html.haml
+++ b/app/views/projects/issues/new.html.haml
@@ -1,4 +1,8 @@
 - page_title "New Issue"
 = render "header_title"
 
+%h3.page-title
+  New Issue
+%hr
+
 = render "form"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index f01bf2505dad68e7e8b91463af86244f1b5a5bba..f931a0d3b92bcdfc6f2bfb8830f6f23ebfe43ada 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,52 +1,67 @@
-- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
+- page_title           "#{@issue.title} (##{@issue.iid})", "Issues"
+- page_description     @issue.description
+- page_card_attributes @issue.card_attributes
+
 = render "header_title"
 
 .issue
+  .detail-page-header
+    .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"} Closed
+    .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"} Open
+    %span.identifier
+      Issue ##{@issue.iid}
+    %span.creator
+      &middot;
+      opened by #{link_to_member(@project, @issue.author, size: 24)}
+      &middot;
+      = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
+      - if @issue.updated_at != @issue.created_at
+        %span
+          &middot;
+          = icon('edit', title: 'edited')
+          = time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
+
+    .pull-right
+      - if can?(current_user, :create_issue, @project)
+        = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New Issue', id: 'new_issue_link' do
+          = icon('plus')
+          New Issue
+      - if can?(current_user, :update_issue, @issue)
+        = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen Issue'
+        = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close Issue'
+
+        = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
+          = icon('pencil-square-o')
+          Edit
+
   .issue-details.issuable-details
-    .page-title
-      .issue-box{ class: issue_box_class(@issue) }
-        - if @issue.closed?
-          Closed
-        - else
-          Open
-      %span.issue-id Issue ##{@issue.iid}
-      %span.creator
-        &middot; created by #{link_to_member(@project, @issue.author, size: 24)}
-        &middot;
-        = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
-        - if @issue.updated_at != @issue.created_at
-          %span
-            &middot;
-            = icon('edit', title: 'edited')
-            = time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
-
-      .pull-right
-        - if can?(current_user, :create_issue, @project)
-          = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-grouped new-issue-link', title: 'New Issue', id: 'new_issue_link' do
-            = icon('plus')
-            New Issue
-        - if can?(current_user, :update_issue, @issue)
-          - if @issue.closed?
-            = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen'
-          - else
-            = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close', title: 'Close Issue'
-
-          = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-grouped issuable-edit' do
-            = icon('pencil-square-o')
-            Edit
-
-    .gray-content-block.middle-block
-      %h2.issue-title
-        = gfm escape_once(@issue.title)
+    .detail-page-description.gray-content-block.second-block
+      %h2.title
+        = markdown escape_once(@issue.title), pipeline: :single_line
       %div
         - if @issue.description.present?
           .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
             .wiki
               = preserve do
-                = markdown(@issue.description)
+                = markdown(@issue.description, cache_key: [@issue, "description"])
             %textarea.hidden.js-task-list-field
               = @issue.description
-  - if @closed_by_merge_requests.present?
-    = render 'projects/issues/closed_by_box'
-  .issue-discussion
-    = render 'projects/issues/discussion'
+
+        .merge-requests
+          = render 'merge_requests'
+
+    .gray-content-block.second-block.oneline-block
+      = render 'votes/votes_block', votable: @issue
+
+    - if @closed_by_merge_requests.present?
+      = render 'projects/issues/closed_by_box'
+
+    .row
+      %section.col-md-9
+        .issuable-discussion
+          = render 'projects/issues/discussion'
+
+      %aside.col-md-3
+        = render 'shared/issuable/sidebar', issuable: @issue
+
+      = render 'shared/show_aside'
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index b7735aaf3c1b8dfc636552bac06102d09a4bf97d..2f0f3fcfb0657b340230b24d5d1d538e23707c85 100644
--- a/app/views/projects/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
@@ -1,3 +1,3 @@
-$('.context').html("#{escape_javascript(render 'shared/issuable/context', issuable: @issue)}");
-$('.context').effect('highlight')
+$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}");
+$('.issuable-sidebar').parent().effect('highlight')
 new Issue();
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index 4cf13492e99cd4cfe0a67b3a2b0a4f69f9c4b93f..5ce2a7b985d542f002e08ce5d8e295a3668f3f82 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -10,9 +10,9 @@
   .form-group
     = f.label :title, class: 'control-label'
     .col-sm-10
-      = f.text_field :title, class: "form-control js-quick-submit", required: true
+      = f.text_field :title, class: "form-control js-quick-submit", required: true, autofocus: true
   .form-group
-    = f.label :color, "Background Color", class: 'control-label'
+    = f.label :color, "Background color", class: 'control-label'
     .col-sm-10
       .input-group
         .input-group-addon.label-color-preview &nbsp;
@@ -28,6 +28,8 @@
             &nbsp;
 
   .form-actions
-    = f.submit 'Save', class: 'btn btn-save js-save-button'
+    - if @label.persisted?
+      = f.submit 'Save changes', class: 'btn btn-save js-save-button'
+    - else
+      = f.submit 'Create Label', class: 'btn btn-create js-save-button'
     = link_to "Cancel", namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel'
-
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index c6ebfa281a149f9e5ba4eaf8e451c1718ef5d8ea..b70a9fc9fe50ecf57526eb0c63a362fdac45568d 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -7,4 +7,4 @@
 
     - if can? current_user, :admin_label, @project
       = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm'
-      = link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
+      = link_to 'Delete', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index bc4ab0ca27c3679a3e73b321cea5537ad97118c1..675a805e12fe1d4caec6a35d80917716cc876597 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -1,11 +1,7 @@
 - page_title "Edit", @label.name, "Labels"
 = render "header_title"
 
-%h3
-  Edit label
-  %span.light #{@label.name}
-.back-link
-  = link_to namespace_project_labels_path(@project.namespace, @project) do
-    &larr; To labels list
+%h3.page-title
+  Edit Label
 %hr
 = render 'form'
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index fb784ee5f4f4f686432ca1cf62c6f8b88d71d0ec..9081bcfe9b35ae7a899c2f1ffe3b7b3083cc490a 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -4,6 +4,7 @@
 .gray-content-block.top-block
   - if can? current_user, :admin_label, @project
     = link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do
+      = icon('plus')
       New label
   .oneline
     Labels can be applied to issues and merge requests.
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index 342ad4f3f95791a22ad05bde4f0829e74fe04083..e20fd7d6891fc40d492bbff755b8d03d6ea51af8 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,9 +1,7 @@
 - page_title "New Label"
 = render "header_title"
 
-%h3 New label
-.back-link
-  = link_to namespace_project_labels_path(@project.namespace, @project) do
-    &larr; To labels list
+%h3.page-title
+  New Label
 %hr
 = render 'form'
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index cb75bd8c5bae19348e21a029f42abad92bef0580..bff3c3b283ddf33a3fee9104980120eb62136021 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -1,35 +1,8 @@
 - content_for :note_actions do
   - if can?(current_user, :update_merge_request, @merge_request)
     - if @merge_request.open?
-      = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
+      = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
     - if @merge_request.closed?
-      = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
+      = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
 
-= render 'shared/show_aside'
-
-.gray-content-block.second-block.oneline-block
-  .row
-    .col-md-9
-      .votes-holder.pull-right
-        #votes= render 'votes/votes_block', votable: @merge_request
-      = render "projects/merge_requests/show/participants"
-    .col-md-3
-      .input-group.cross-project-reference
-        %span.slead.has_tooltip{title: 'Cross-project reference'}
-          = cross_project_reference(@project, @merge_request)
-        = clipboard_button
-
-.row
-  %section.col-md-9
-    = render "projects/notes/notes_with_form"
-  %aside.col-md-3
-    .issuable-affix
-      .context
-        = render 'shared/issuable/context', issuable: @merge_request
-
-      - if @merge_request.labels.any?
-        .issuable-context-title
-          %label Labels
-        .merge-request-show-labels
-          - @merge_request.labels.each do |label|
-            = link_to_label(label)
+#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 9cf389dbe3874e79ab6d82184b1beb574c9aa247..3e4ab09c6d41366914838bbd3830edf769436fc9 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,6 +1,5 @@
 = form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
-  .merge-request-form-info
-    = render 'shared/issuable/form', f: f, issuable: @merge_request
+  = render 'shared/issuable/form', f: f, issuable: @merge_request
 
 :javascript
   $('.assign-to-me-link').on('click', function(e){
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 83e8ad11989b684670c44719afeb0630d2a25714..a051729dc3202221a75da4595df695a9f4eaead2 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,50 +1,61 @@
-- ci_commit = merge_request.ci_commit
 %li{ class: mr_css_classes(merge_request) }
   .merge-request-title
     %span.merge-request-title-text
       = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
-    .merge-request-labels
-      - merge_request.labels.each do |label|
-        = link_to_label(label, project: merge_request.project)
-    .pull-right.light
-      - if ci_commit
-        = render_ci_status(ci_commit)
+    %ul.controls.light
       - if merge_request.merged?
-        %span
-          %i.fa.fa-check
+        %li
           MERGED
       - elsif merge_request.closed?
-        %span
-          %i.fa.fa-ban
+        %li
+          = icon('ban')
           CLOSED
-      - note_count = merge_request.mr_and_commit_notes.user.count
+
+      - if merge_request.ci_commit
+        %li
+          = render_ci_status(merge_request.ci_commit)
+
+      - if merge_request.open? && merge_request.broken?
+        %li
+          = link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
+            = icon('exclamation-triangle')
+
       - if merge_request.assignee
-        &nbsp;
-        = link_to_member(merge_request.source_project, merge_request.assignee, name: false)
+        %li
+          = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
+
+      - note_count = merge_request.mr_and_commit_notes.user.count
       - if note_count > 0
-        &nbsp;
-        %span
-          %i.fa.fa-comments
-          = note_count
+        %li
+          = link_to merge_request_path(merge_request) + "#notes" do
+            = icon('comments')
+            = note_count
       - else
-        &nbsp;
-        %span.merge-request-no-comments
-          %i.fa.fa-comments
-          = 0
+        %li
+          = link_to merge_request_path(merge_request) + "#notes", class: "merge-request-no-comments" do
+            = icon('comments')
+            = note_count
 
   .merge-request-info
-    = "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe
-    - if merge_request.milestone_id?
-      &nbsp;
-      %span
-        %i.fa.fa-clock-o
-        = merge_request.milestone.title
+    \##{merge_request.iid} &middot;
+    opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
+    by #{link_to_member(@project, merge_request.author, avatar: false)}
     - if merge_request.target_project.default_branch != merge_request.target_branch
       &nbsp;
-      %span
-        %i.fa.fa-code-fork
+      = link_to namespace_project_commits_path(merge_request.project.namespace, merge_request.project, merge_request.target_branch) do
+        = icon('code-fork')
         = merge_request.target_branch
+    - if merge_request.milestone
+      &nbsp;
+      = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
+        = icon('clock-o')
+        = merge_request.milestone.title
+    - if merge_request.labels.any?
+      &nbsp;
+      - merge_request.labels.each do |label|
+        = link_to_label(label, project: merge_request.project)
     - if merge_request.tasks?
+      &nbsp;
       %span.task-status
         = merge_request.task_status
 
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index d9eff1f9320aeaeb0a779cc4fad575158ca4d71e..236a545c8407a4bd5c0fc867235ec1a50404ab3f 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -1,4 +1,5 @@
-%p.lead Compare branches for new Merge Request
+%h3.page-title
+  New Merge Request
 
 = form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
   .hide.alert.alert-danger.mr-compare-errors
@@ -10,7 +11,7 @@
         .panel-body
           = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted?, required: true })
           &nbsp;
-          = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2', required: true})
+          = f.select(:source_branch, @merge_request.source_branches, { include_blank: true }, { class: 'source_branch select2 span2', required: true, data: { placeholder: "Select source branch" } })
         .panel-footer
           .mr_source_commit
 
@@ -22,7 +23,7 @@
           - projects =  @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
           = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted?, required: true })
           &nbsp;
-          = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2', required: true})
+          = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', required: true, data: { placeholder: "Select target branch" } })
         .panel-footer
           .mr_target_commit
 
@@ -37,7 +38,7 @@
         %h4 Compare failed
         %p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches.
     - else
-      .light-well.append-bottom-10
+      .light-well.append-bottom-default
         .center
           %h4
             There isn't anything to merge.
@@ -51,8 +52,8 @@
               are the same.
 
 
-  %div
-    = f.submit 'Compare branches', class: "btn btn-new mr-compare-btn"
+  .form-actions
+    = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
 
 :javascript
   var source_branch = $("#merge_request_source_branch")
@@ -86,4 +87,3 @@
       return;
     }
   });
-
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 6244d3ba0b440c52d943a2cb7d1175d34deb9011..a14943b15d3f1532ac6b0e94fb3162817988a16c 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -1,5 +1,5 @@
 %h3.page-title
-  New merge request
+  New Merge Request
 %p.slead
   - source_title, target_title = format_mr_branch_names(@merge_request)
   From
@@ -11,27 +11,31 @@
     = link_to 'Change branches', mr_change_branches_path(@merge_request)
 %hr
 = form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
-  .merge-request-form-info
-    = render 'shared/issuable/form', f: f, issuable: @merge_request
-    = f.hidden_field :source_project_id
-    = f.hidden_field :source_branch
-    = f.hidden_field :target_project_id
-    = f.hidden_field :target_branch
+  = render 'shared/issuable/form', f: f, issuable: @merge_request
+  = f.hidden_field :source_project_id
+  = f.hidden_field :source_branch
+  = f.hidden_field :target_project_id
+  = f.hidden_field :target_branch
 
 .mr-compare.merge-request
-  %ul.merge-request-tabs
+  %ul.merge-request-tabs.center-top-menu.no-top.no-bottom
     %li.commits-tab
-      = link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
+      = link_to url_for(params), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
         Commits
         %span.badge= @commits.size
+    - if @ci_commit
+      %li.builds-tab.active
+        = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
+          Builds
+          %span.badge= @statuses.size
     %li.diffs-tab.active
-      = link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
+      = link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
         Changes
         %span.badge= @diffs.size
 
   .tab-content
     #commits.commits.tab-pane
-      = render "projects/commits/commits", project: @project
+      = render "projects/merge_requests/show/commits"
     #diffs.diffs.tab-pane.active
       - if @diffs.present?
         = render "projects/diffs/diffs", diffs: @diffs, project: @project
@@ -43,6 +47,9 @@
         .alert.alert-danger
           %h4 This comparison includes a huge diff.
           %p To preserve performance the line changes are not shown.
+    - if @ci_commit
+      #builds.builds.tab-pane
+        = render "projects/merge_requests/show/builds"
 
 :javascript
   $('.assign-to-me-link').on('click', function(e){
@@ -57,4 +64,3 @@
     diffs_loaded: true,
     commits_loaded: true
   });
-
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index eeaa72ed21bebf3e15a4dc7ea50d3abbcfec3e02..ba7c2c01e93a7ac6760633a325672ab9db40b1e8 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,14 +1,18 @@
-- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+- page_title           "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+- page_description     @merge_request.description
+- page_card_attributes @merge_request.card_attributes
+
 = render "header_title"
 
 - if params[:view] == 'parallel'
   - fluid_layout true
 
 .merge-request{'data-url' => merge_request_path(@merge_request)}
+  = render "projects/merge_requests/show/mr_title"
+
   .merge-request-details.issuable-details
-    = render "projects/merge_requests/show/mr_title"
     = render "projects/merge_requests/show/mr_box"
-    .append-bottom-20.mr-source-target.prepend-top-default
+    .append-bottom-default.mr-source-target.prepend-top-default
       - if @merge_request.open?
         .pull-right
           - if @merge_request.source_branch_exists?
@@ -26,44 +30,62 @@
               %li= link_to "Plain Diff",    merge_request_path(@merge_request, format: :diff)
       .normal
         %span Request to merge
-        %span.label-branch #{source_branch_with_namespace(@merge_request)}
+        %span.label-branch= source_branch_with_namespace(@merge_request)
         %span into
-        %span.label-branch #{@merge_request.target_branch}
+        = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
+          = @merge_request.target_branch
 
     = render "projects/merge_requests/show/how_to_merge"
     = render "projects/merge_requests/widget/show.html.haml"
 
-    - if @merge_request.open? && @merge_request.can_be_merged?
-      .light.append-bottom-20
+    - if @merge_request.open? && @merge_request.source_branch_exists? && @merge_request.can_be_merged? && @merge_request.can_be_merged_by?(current_user)
+      .light.prepend-top-default
         You can also accept this merge request manually using the
         = succeed '.' do
           = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
 
-  - if @commits.present?
-    %ul.merge-request-tabs
-      %li.notes-tab
-        = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do
-          Discussion
-          %span.badge= @merge_request.mr_and_commit_notes.user.count
-      %li.commits-tab
-        = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
-          Commits
-          %span.badge= @commits.size
-      %li.diffs-tab
-        = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
-          Changes
-          %span.badge= @merge_request.diffs.size
+    - if @commits.present?
+      %ul.merge-request-tabs.center-top-menu.no-top.no-bottom
+        %li.notes-tab
+          = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
+            Discussion
+            %span.badge= @merge_request.mr_and_commit_notes.user.count
+        %li.commits-tab
+          = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
+            Commits
+            %span.badge= @commits.size
+        - if @ci_commit
+          %li.builds-tab
+            = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do
+              Builds
+              %span.badge= @statuses.size
+        %li.diffs-tab
+          = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
+            Changes
+            %span.badge= @merge_request.diffs.size
+
+      .tab-content
+        #notes.notes.tab-pane.voting_notes
+          .gray-content-block.second-block.oneline-block
+            = render 'votes/votes_block', votable: @merge_request
+
+          .row
+            %section.col-md-9
+              .issuable-discussion
+                = render "projects/merge_requests/discussion"
+            %aside.col-md-3
+              = render 'shared/issuable/sidebar', issuable: @merge_request
+            = render 'shared/show_aside'
 
-  .tab-content
-    #notes.notes.tab-pane.voting_notes
-      = render "projects/merge_requests/discussion"
-    #commits.commits.tab-pane
-      - # This tab is always loaded via AJAX
-    #diffs.diffs.tab-pane
-      - # This tab is always loaded via AJAX
+        #commits.commits.tab-pane
+          - # This tab is always loaded via AJAX
+        #builds.builds.tab-pane
+          - # This tab is always loaded via AJAX
+        #diffs.diffs.tab-pane
+          - # This tab is always loaded via AJAX
 
-  .mr-loading-status
-    = spinner
+      .mr-loading-status
+        = spinner
 
 :javascript
   var merge_request;
diff --git a/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml b/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml
new file mode 100644
index 0000000000000000000000000000000000000000..eab5be488b5d32faed0037aa1b9004e5901dc88f
--- /dev/null
+++ b/app/views/projects/merge_requests/cancel_merge_when_build_succeeds.js.haml
@@ -0,0 +1,2 @@
+:plain
+  $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/accept'))}");
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
index 303ca0a880b1a5ea5fb65f613868d61cbdd8f2b2..fc62bb5bce9ae72d5e310ac5e33b2c049aa1a8fe 100644
--- a/app/views/projects/merge_requests/edit.html.haml
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -2,6 +2,6 @@
 = render "header_title"
 
 %h3.page-title
-  = "Edit merge request ##{@merge_request.iid}"
+  Edit Merge Request ##{@merge_request.iid}
 %hr
 = render 'form'
diff --git a/app/views/projects/merge_requests/merge.js.haml b/app/views/projects/merge_requests/merge.js.haml
index 33321651e32fcf9ff2bb4b9cb43db4eacfa139b9..92ce479d463d13ea6495e499864dd62ac41914eb 100644
--- a/app/views/projects/merge_requests/merge.js.haml
+++ b/app/views/projects/merge_requests/merge.js.haml
@@ -1,6 +1,10 @@
-- if @status
+- case @status
+- when :success
   :plain
-    merge_request_widget.mergeInProgress();
+    merge_request_widget.mergeInProgress(#{params[:should_remove_source_branch] == '1'});
+- when :merge_when_build_succeeds
+  :plain
+    $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_build_succeeds'))}");
 - else
   :plain
     $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/reload'))}");
diff --git a/app/views/projects/merge_requests/new.html.haml b/app/views/projects/merge_requests/new.html.haml
index 9fdde80c6d9dcc2dc71eb9d24e85d8d9583e5e56..d259968030e92b0d7e02c0ef293d4cfca70f0f44 100644
--- a/app/views/projects/merge_requests/new.html.haml
+++ b/app/views/projects/merge_requests/new.html.haml
@@ -1,7 +1,7 @@
 - page_title "New Merge Request"
 = render "header_title"
 
-- if @merge_request.can_be_created
+- if @merge_request.can_be_created && !params[:change_branches]
   = render 'new_submit'
 - else
   = render 'new_compare'
diff --git a/app/views/projects/merge_requests/show/_builds.html.haml b/app/views/projects/merge_requests/show/_builds.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..307a75d02cac0c3ce4f6bf17960e564b76067567
--- /dev/null
+++ b/app/views/projects/merge_requests/show/_builds.html.haml
@@ -0,0 +1 @@
+= render "projects/commit/builds", link_to_commit: true
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index 478054db517a5d3eaf2530608f769104fb58adc6..7f904ec42a096e015c4e80d0522015272279f6a9 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -1,4 +1,4 @@
-.gray-content-block.second-block.oneline-block
+.gray-content-block.middle-block.oneline-block
   = icon("sort-amount-desc")
   Most recent commits displayed first
 
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index 98f0357ce4ea4699632e01c2a6e469a68aa1eadd..877cc3d744b9bf6855d52efd241a78691e0a5139 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -8,8 +8,8 @@
         %p
           %strong Step 1.
           Fetch and check out the branch for this merge request
-        = clipboard_button
-        %pre.dark
+        = clipboard_button(clipboard_target: 'pre#merge-info-1')
+        %pre.dark#merge-info-1
           - if @merge_request.for_fork?
             :preserve
               git fetch #{h @merge_request.source_project.http_url_to_repo} #{h @merge_request.source_branch}
@@ -25,8 +25,8 @@
         %p
           %strong Step 3.
           Merge the branch and fix any conflicts that come up
-        = clipboard_button
-        %pre.dark
+        = clipboard_button(clipboard_target: 'pre#merge-info-3')
+        %pre.dark#merge-info-3
           - if @merge_request.for_fork?
             :preserve
               git checkout #{h @merge_request.target_branch}
@@ -38,8 +38,8 @@
         %p
           %strong Step 4.
           Push the result of the merge to GitLab
-        = clipboard_button
-        %pre.dark
+        = clipboard_button(clipboard_target: 'pre#merge-info-4')
+        %pre.dark#merge-info-4
           :preserve
             git push origin #{h @merge_request.target_branch}
         - unless @merge_request.can_be_merged_by?(current_user)
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index b4f62a758900e92452fa66bba86dce737218a938..0f81e5e891424ff42f77c0011efc7e87cc79288c 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,12 +1,12 @@
-.gray-content-block.middle-block
-  %h2.issue-title
-    = gfm escape_once(@merge_request.title)
+.detail-page-description.gray-content-block.second-block
+  %h2.title
+    = markdown escape_once(@merge_request.title), pipeline: :single_line
 
   %div
     - if @merge_request.description.present?
       .description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''}
         .wiki
           = preserve do
-            = markdown(@merge_request.description)
+            = markdown(@merge_request.description, cache_key: [@merge_request, "description"])
         %textarea.hidden.js-task-list-field
           = @merge_request.description
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 2bf9cd597a4d8d8bec8b4a73ac3305094fafafa2..fc6fb2a0d42f56b243870a06a83d27d722a7d6ee 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,10 +1,11 @@
-.page-title
-  .issue-box{ class: issue_box_class(@merge_request) }
+.detail-page-header
+  .status-box{ class: status_box_class(@merge_request) }
     = @merge_request.state_human_name
-  %span.issue-id Merge Request ##{@merge_request.iid}
+  %span.identifier
+    Merge Request ##{@merge_request.iid}
   %span.creator
     &middot;
-    created by #{link_to_member(@project, @merge_request.author, size: 24)}
+    opened by #{link_to_member(@project, @merge_request.author, size: 24)}
     &middot;
     = time_ago_with_tooltip(@merge_request.created_at)
     - if @merge_request.updated_at != @merge_request.created_at
@@ -16,9 +17,9 @@
   .issue-btn-group.pull-right
     - if can?(current_user, :update_merge_request, @merge_request)
       - if @merge_request.open?
-        = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request"
-        = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn btn-grouped issuable-edit", id: "edit_merge_request" do
+        = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
+        = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
           %i.fa.fa-pencil-square-o
           Edit
       - if @merge_request.closed?
-        = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request"
+        = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
diff --git a/app/views/projects/merge_requests/show/_participants.html.haml b/app/views/projects/merge_requests/show/_participants.html.haml
deleted file mode 100644
index c67afe963e7be2abdf1e1906c4b4d7d9299d66f9..0000000000000000000000000000000000000000
--- a/app/views/projects/merge_requests/show/_participants.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-.participants
-  %span #{@participants.count} participants
-  - @participants.each do |participant|
-    = link_to_member(@project, participant, name: false, size: 24)
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
index 25583b2cc6f0aba1e175868501ea88a11e8a8d13..93db65ddf798524b60a8bc8e15bfb1f4c5cfc134 100644
--- a/app/views/projects/merge_requests/update.js.haml
+++ b/app/views/projects/merge_requests/update.js.haml
@@ -1,3 +1,3 @@
-$('.context').html("#{escape_javascript(render 'shared/issuable/context', issuable: @merge_request)}");
-$('.context').effect('highlight')
+$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}");
+$('.issuable-sidebar').parent().effect('highlight')
 merge_request = new MergeRequest();
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index ba5ad22bca750a83285150c78bb4ee6f2e8a3db4..b05ab8692156a0a8fce868d58b311d7537bd6912 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,30 +1,33 @@
-- ci_commit = @merge_request.ci_commit
-- if ci_commit
-  - status = ci_commit.status
+- if @ci_commit
   .mr-widget-heading
-    .ci_widget{class: "ci-#{status}"}
-      = ci_status_icon(ci_commit)
-      %span CI build #{status}
-      for #{@merge_request.last_commit_short_sha}.
+    .ci_widget{class: "ci-#{@ci_commit.status}"}
+      = ci_status_icon(@ci_commit)
+      %span
+        Build
+        = ci_status_label(@ci_commit)
+      for
+      = succeed "." do
+        = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace"
       %span.ci-coverage
-      = link_to "View build details", ci_status_path(ci_commit)
+      = link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'}
 
 - elsif @merge_request.has_ci?
   - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
   - # Remove in later versions when services like Jenkins will set CI status via Commit status API
   .mr-widget-heading
-    - [:success, :skipped, :canceled, :failed, :running, :pending].each do |status|
+    - %w[success skipped canceled failed running pending].each do |status|
       .ci_widget{class: "ci-#{status}", style: "display:none"}
-        - if status == :success
-          - status = "passed"
-          = icon("check-circle")
-        - else
-          = icon("circle")
-        %span CI build #{status}
-        for #{@merge_request.last_commit_short_sha}.
+        = ci_icon_for_status(status)
+        %span
+          CI build
+          = ci_label_for_status(status)
+        for
+        - commit = @merge_request.last_commit
+        = succeed "." do
+          = link_to commit.short_id, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, commit), class: "monospace"
         %span.ci-coverage
-        - if ci_build_details_path(@merge_request)
-          = link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
+        - if details_path = ci_build_details_path(@merge_request)
+          = link_to "View details", details_path, :"data-no-turbolink" => "data-no-turbolink"
 
     .ci_widget
       = icon("spinner spin")
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index a788fcea23f4daaf4ba91b5ed04084aa9e0f681d..d1d602eecdcdc08986ed25822956416b2c470904 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -7,18 +7,16 @@
         by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)}
         #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
     %div
-      - if !@merge_request.source_branch_exists?
-        = succeed '.' do
-          The changes were merged into
-          %span.label-branch= @merge_request.target_branch
+      - if !@merge_request.source_branch_exists? || (params[:delete_source] == 'true')
+        The changes were merged into
+        #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
         The source branch has been removed.
 
-      - elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch)
+      - elsif @merge_request.can_remove_source_branch?(current_user)
         .remove_source_branch_widget
           %p
-            = succeed '.' do
-              The changes were merged into
-              %span.label-branch= @merge_request.target_branch
+            The changes were merged into
+            #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
             You can remove the source branch now.
           = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
             %i.fa.fa-times
@@ -31,7 +29,7 @@
         .remove_source_branch_in_progress.hide
           %p
             = icon('spinner spin')
-            Removing source branch '#{@merge_request.source_branch}'. Please wait. This page will be automatically reload.
+            Removing source branch '#{@merge_request.source_branch}'. Please wait, this page will be automatically reloaded.
 
         :javascript
           $('.remove_source_branch').on('click', function() {
@@ -48,5 +46,3 @@
             $('.remove_source_branch_in_progress').hide();
             $('.remove_source_branch_widget.failed').show();
           });
-
-
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 0aad9bb3e88e0507eb56af53618271f2e525c499..55dbae598d3c88a8ef917c26a8813b00b3d574bb 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -13,6 +13,8 @@
       = render 'projects/merge_requests/widget/open/conflicts'
     - elsif @merge_request.work_in_progress?
       = render 'projects/merge_requests/widget/open/wip'
+    - elsif @merge_request.merge_when_build_succeeds?
+      = render 'projects/merge_requests/widget/open/merge_when_build_succeeds'
     - elsif !@merge_request.can_be_merged_by?(current_user)
       = render 'projects/merge_requests/widget/open/not_allowed'
     - elsif @merge_request.can_be_merged?
@@ -24,4 +26,4 @@
         %i.fa.fa-check
         Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)}
         = succeed '.' do
-          != gfm(issues_sentence(@closes_issues))
+          != markdown issues_sentence(@closes_issues), pipeline: :gfm
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index 9b31014b581768aea12f28e6fb4d0068816aad20..d9a1730a8bc96f25c81d021d255ad33c36334aad 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -1,28 +1,60 @@
-- status_class = @merge_request.ci_commit ? " ci-#{@merge_request.ci_commit.status}" : nil
+- status_class = @ci_commit ? " ci-#{@ci_commit.status}" : nil
 
 = form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
   = hidden_field_tag :authenticity_token, form_authenticity_token
   .accept-merge-holder.clearfix.js-toggle-container
-    .accept-action
-      = f.button class: "btn btn-create accept_merge_request#{status_class}" do
-        Accept Merge Request
-    - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
-      .accept-control.checkbox
-        = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
-          = check_box_tag :should_remove_source_branch
-          Remove source branch
-    .accept-control.right
-      = link_to "#", class: "modify-merge-commit-link js-toggle-button" do
-        = icon('edit')
-        Modify commit message
-    .js-toggle-content.hide.prepend-top-20
+    .clearfix
+      .accept-action
+        - if @ci_commit && @ci_commit.active?
+          %span.btn-group
+            = button_tag class: "btn btn-create js-merge-button merge_when_build_succeeds" do
+              Merge When Build Succeeds
+            = button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown' do
+              %span.caret
+              %span.sr-only
+                Select Merge Moment
+            %ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' }
+              %li
+                = link_to "#", class: "merge_when_build_succeeds" do
+                  = icon('check fw')
+                  Merge When Build Succeeds
+              %li
+                = link_to "#", class: "accept_merge_request" do
+                  = icon('warning fw')
+                  Merge Immediately
+        - else
+          = f.button class: "btn btn-create btn-grouped js-merge-button accept_merge_request #{status_class}" do
+            Accept Merge Request
+      - if @merge_request.can_remove_source_branch?(current_user)
+        .accept-control.checkbox
+          = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
+            = check_box_tag :should_remove_source_branch
+            Remove source branch
+      .accept-control.right
+        = link_to "#", class: "modify-merge-commit-link js-toggle-button" do
+          = icon('edit')
+          Modify commit message
+    .js-toggle-content.hide.prepend-top-default
       = render 'shared/commit_message_container', params: params,
           text: @merge_request.merge_commit_message,
           rows: 14, hint: true
 
+    = hidden_field_tag :merge_when_build_succeeds, "", autocomplete: "off"
+
   :javascript
-    $('.accept-mr-form').on('ajax:before', function() {
-      var btn = $('.accept_merge_request');
-      btn.disable();
-      btn.html("<i class='fa fa-spinner fa-spin'></i> Merge in progress");
+    $('.accept-mr-form').on('ajax:send', function() {
+      $(".accept-mr-form :input").disable();
+    });
+
+    $('.accept_merge_request').on('click', function() {
+      $('.js-merge-button').html("<i class='fa fa-spinner fa-spin'></i> Merge in progress");
+    });
+
+    $('.merge_when_build_succeeds').on('click', function() {
+      $("#merge_when_build_succeeds").val("1");
+    });
+
+    $('.js-merge-dropdown a').on('click', function(e) {
+      e.preventDefault();
+      $(this).closest("form").submit();
     });
diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..2168294c683936502a6f4ade730572e77d7e172b
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
@@ -0,0 +1,26 @@
+%h4
+  Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)}
+  to be merged automatically when the build succeeds.
+%div
+  - should_remove_source_branch = @merge_request.merge_params["should_remove_source_branch"].present?
+  %p
+    = succeed '.' do
+      The changes will be merged into
+      %span.label-branch= @merge_request.target_branch
+    - if should_remove_source_branch
+      The source branch will be removed.
+    - else
+      The source branch will not be removed.
+
+  - remove_source_branch_button = @merge_request.can_remove_source_branch?(current_user) && !should_remove_source_branch && @merge_request.merge_user == current_user
+  - user_can_cancel_automatic_merge = @merge_request.can_cancel_merge_when_build_succeeds?(current_user)
+  - if remove_source_branch_button || user_can_cancel_automatic_merge
+    .clearfix.prepend-top-10
+      - if remove_source_branch_button
+        = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
+          = icon('times')
+          Remove Source Branch When Merged
+
+      - if user_can_cancel_automatic_merge
+        = link_to cancel_merge_when_build_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-warning btn-sm" do
+          Cancel Automatic Merge
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 24879b19d2bfc7a3aa7bdf46919da0d8f9cf110e..39aa2437e188d5bb9c0c8d40a85da2073dada81f 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -1,10 +1,3 @@
-%h3.page-title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.iid}"
-.back-link
-  = link_to namespace_project_milestones_path(@project.namespace, @project) do
-    &larr; To milestones
-
-%hr
-
 = form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-requires-input'}  do |f|
   -if @milestone.errors.any?
     .alert.alert-danger
@@ -16,8 +9,7 @@
       .form-group
         = f.label :title, "Title", class: "control-label"
         .col-sm-10
-          = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true
-          %p.hint Required
+          = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true
       .form-group.milestone-description
         = f.label :description, "Description", class: "control-label"
         .col-sm-10
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 5e93d55b1fbb3e617d0f6da1e046a869644b3dd1..d6a44c9f0a1ce776e1267190b7c80da39976bf05 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -18,11 +18,7 @@
 
   .row
     .col-sm-6
-      - if milestone.expired? and not milestone.closed?
-        %span.cred (Expired)
-      - if milestone.expires_at
-        %span
-          = milestone.expires_at
+      = render 'shared/milestone_expired', milestone: milestone
     .col-sm-6
       - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
         = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do
@@ -31,4 +27,4 @@
         = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
         = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
           %i.fa.fa-trash-o
-          Remove
+          Delete
diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml
index e9dc0b77462db046504691acfe848367a2eb404b..43f8863163dadbd50cbdc1603e899c9a2e4c6e17 100644
--- a/app/views/projects/milestones/edit.html.haml
+++ b/app/views/projects/milestones/edit.html.haml
@@ -1,3 +1,9 @@
 - page_title "Edit", @milestone.title, "Milestones"
 = render "header_title"
+
+%h3.page-title
+  Edit Milestone ##{@milestone.iid}
+
+%hr
+
 = render "form"
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index a207385bd4351984a40d0073b66c6cec30c6377b..114b06457a52aead1faea05a1e3c89e506527ae0 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,15 +1,18 @@
 - page_title "Milestones"
 = render "header_title"
-= render 'shared/milestones_filter'
 
-.gray-content-block
-  .pull-right
-    - if can? current_user, :admin_milestone, @project
+
+.project-issuable-filter
+  .controls
+    - if can?(current_user, :admin_milestone, @project)
       = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
         %i.fa.fa-plus
         New Milestone
-  .oneline
-    Milestone allows you to group issues and set due date for it
+
+  = render 'shared/milestones_filter'
+
+.gray-content-block
+  Milestone allows you to group issues and set due date for it
 
 .milestones
   %ul.content-list
diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index 9ba9acb6f77ea30dca7c86a18381d188f26a64bd..0d016f7831391f3f3a5d457679f7b275063fb0a6 100644
--- a/app/views/projects/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
@@ -1,3 +1,9 @@
 - page_title "New Milestone"
 = render "header_title"
+
+%h3.page-title
+  New Milestone
+
+%hr
+
 = render "form"
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 3a898dfbcfd1f2406da96d2afc8e7c71e4cfd77d..1670ea8741a3a07065f93f38a89e4dc13b771812 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,46 +1,52 @@
-- page_title @milestone.title, "Milestones"
+- page_title       @milestone.title, "Milestones"
+- page_description @milestone.description
+
 = render "header_title"
 
-%h4.page-title
-  .issue-box{ class: issue_box_class(@milestone) }
+.detail-page-header
+  .status-box{ class: status_box_class(@milestone) }
     - if @milestone.closed?
       Closed
     - elsif @milestone.expired?
       Expired
     - else
       Open
-  Milestone ##{@milestone.iid}
-  %small.creator
-    = @milestone.expires_at
+  %span.identifier
+    Milestone ##{@milestone.iid}
+  - if @milestone.expires_at
+    %span.creator
+      &middot;
+      = @milestone.expires_at
   .pull-right
     - if can?(current_user, :admin_milestone, @project)
-      = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do
-        %i.fa.fa-pencil-square-o
-        Edit
       - if @milestone.active?
         = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
       - else
         = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
+
       = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do
         %i.fa.fa-trash-o
-        Remove
+        Delete
+
+      = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do
+        %i.fa.fa-pencil-square-o
+        Edit
+
+.detail-page-description.gray-content-block.second-block
+  %h2.title
+    = markdown escape_once(@milestone.title), pipeline: :single_line
+  %div
+    - if @milestone.description.present?
+      .description
+        .wiki
+          = preserve do
+            = markdown @milestone.description
 
-%hr
 - if @milestone.issues.any? && @milestone.can_be_closed?
-  .alert.alert-success
+  .alert.alert-success.prepend-top-default
     %span All issues for this milestone are closed. You may close milestone now.
 
-%h3.issue-title
-  = gfm escape_once(@milestone.title)
-%div
-  - if @milestone.description.present?
-    .description
-      .wiki
-        = preserve do
-          = markdown @milestone.description
-
-%hr
-.context
+.context.prepend-top-default
   %p.lead
     Progress:
     #{@milestone.closed_items_count} closed
@@ -51,8 +57,7 @@
     %span.pull-right= @milestone.expires_at
   = milestone_progress_bar(@milestone)
 
-
-%ul.nav.nav-tabs
+%ul.center-top-menu.no-top.no-bottom
   %li.active
     = link_to '#tab-issues', 'data-toggle' => 'tab' do
       Issues
@@ -66,17 +71,21 @@
       Participants
       %span.badge= @users.count
 
-  .pull-right
-    - if can?(current_user, :create_issue, @project)
-      = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn  btn-grouped", title: "New Issue" do
-        %i.fa.fa-plus
-        New Issue
-    - if can?(current_user, :read_issue, @project)
-      = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn  edit-milestone-link btn-grouped"
-
 .tab-content
   .tab-pane.active#tab-issues
-    .row
+    .gray-content-block.middle-block
+      .pull-right
+        - if can?(current_user, :create_issue, @project)
+          = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn  btn-grouped", title: "New Issue" do
+            %i.fa.fa-plus
+            New Issue
+        - if can?(current_user, :read_issue, @project)
+          = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
+
+      .oneline
+        All issues in this milestone
+
+    .row.prepend-top-default
       .col-md-4
         = render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned')
       .col-md-4
@@ -85,7 +94,15 @@
         = render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
 
   .tab-pane#tab-merge-requests
-    .row
+    .gray-content-block.middle-block
+      .pull-right
+        - if can?(current_user, :read_merge_request, @project)
+          = link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
+
+      .oneline
+        All merge requests in this milestone
+
+    .row.prepend-top-default
       .col-md-3
         = render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned')
       .col-md-3
@@ -100,6 +117,10 @@
               = render 'merge_request', merge_request: merge_request
 
   .tab-pane#tab-participants
+    .gray-content-block.middle-block
+      .oneline
+        All participants to this milestone
+
     %ul.bordered-list
       - @users.each do |user|
         %li
diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml
index 415c98ec6a6aed0561730427d782f8db9e48a344..28a617538b50e2ff6e5412f4c49e20da662fac87 100644
--- a/app/views/projects/network/_head.html.haml
+++ b/app/views/projects/network/_head.html.haml
@@ -1,3 +1,6 @@
-.append-bottom-20
-  = render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
-  .pull-right.visible-lg.light You can move around the graph by using the arrow keys.
+.gray-content-block.append-bottom-default
+  .tree-ref-holder
+    = render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
+
+  .oneline
+    You can move around the graph by using the arrow keys.
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index 16005161df623103afe5b06ccd24c71a85a7f597..8065663ca2a873740c3ae121399f20be94b2a237 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,5 +1,6 @@
 - page_title "Network", @ref
-= header_title project_title(@project, "Network", namespace_project_network_path(@project.namespace, @project, current_ref))
+= render "projects/commits/header_title"
+= render "projects/commits/head"
 = render "head"
 .project-network
   .controls
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index c9d1fc3da2125a55e34c111df2d5b2362eb1b698..25233112132d3fc6311a3684d77e754eef52dc36 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,5 +1,10 @@
 - page_title    'New Project'
-- header_title  'New Project'
+- header_title  "Projects", root_path
+
+%h3.page-title
+  New Project
+%hr
+
 .project-edit-container
   .project-edit-errors
     = render 'projects/errors'
@@ -11,16 +16,21 @@
           Project path
         .col-sm-10
           .input-group
-            = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true, required: true
-            .input-group-addon
-              \.git
-
-      - if current_user.can_select_namespace?
-        .form-group
-          = f.label :namespace_id, class: 'control-label' do
-            %span Namespace
-          .col-sm-10
-            = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2}
+            - if current_user.can_select_namespace?
+              .input-group-addon
+                = root_url
+              = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2', tabindex: 1}
+              .input-group-addon
+                \/
+            - else
+              .input-group-addon
+                #{root_url}#{current_user.username}/
+            = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
+
+          - if current_user.can_create_group?
+            .help-block
+              Want to house several dependent projects under the same namespace?
+              = link_to "Create a group", new_group_path
 
       - if import_sources_enabled?
         .project-import.js-toggle-container
@@ -90,19 +100,12 @@
           Description
           %span.light (optional)
         .col-sm-10
-          = f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250, tabindex: 3
+          = f.text_area :description, class: "form-control", rows: 3, maxlength: 250, tabindex: 3
       = render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project
 
       .form-actions
         = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
-
-        - if current_user.can_create_group?
-          .pull-right
-            .light.inline
-              .space-right
-                Need a group for several dependent projects?
-            = link_to new_group_path, class: "btn" do
-              Create a group
+        = link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
 
 .save-project-loader.hide
   .center
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index a21c019986a99a9316b8c12fb474a57895824630..3ccda1b381caf9a27210399683ece84899fe49d8 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -6,6 +6,5 @@
       = render 'projects/notes/hints'
 
     .note-form-actions
-      .buttons
-        = f.submit 'Save Comment', class: 'btn btn-primary btn-save btn-grouped js-comment-button'
-        = link_to  'Cancel', '#', class: 'btn btn-cancel note-edit-cancel'
+      = f.submit 'Save Comment', class: 'btn btn-primary btn-save btn-grouped js-comment-button'
+      = link_to  'Cancel', '#', class: 'btn btn-cancel note-edit-cancel'
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 5dd84317e3b56102978c3601a69986713869b8b5..acb6dc52a8e204f7fecfc7d2f70eeabe844041dc 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -12,8 +12,7 @@
     = render 'projects/notes/hints'
     .error-alert
 
-  .note-form-actions
-    .buttons.clearfix
-      = f.submit 'Add Comment', class: "btn btn-green comment-btn btn-grouped js-comment-button"
-      = yield(:note_actions)
-      %a.btn.grouped.js-close-discussion-note-form Cancel
+  .note-form-actions.clearfix
+    = f.submit 'Add Comment', class: "btn btn-nr btn-create comment-btn btn-grouped js-comment-button"
+    = yield(:note_actions)
+    %a.btn.btn-cancel.js-close-discussion-note-form Cancel
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index dd0abc8c74681047c18d08761d7f027f0c60852e..922535e5c4a67fbc22a94684d3c24ef45bdee9e6 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -38,7 +38,7 @@
       .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
         .note-text
           = preserve do
-            = markdown(note.note, {no_header_anchors: true})
+            = markdown(note.note, pipeline: :note, cache_key: [note, "note"])
         - if note_editable?(note)
           = render 'projects/notes/edit_form', note: note
 
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 99c1b0fa43eb8135cd5c572d4d8710e17a9e1815..eb378b4260367a15996999c525430642ae2660fa 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -7,4 +7,4 @@
   = render "projects/notes/form", view: diff_view
 
 :javascript
-  new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
+  var notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index 43e92437cf577e64163efd308ca0526e86c3f1ac..1c2458fa1440aa29cae51edb46d7a02977240f7b 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -4,12 +4,13 @@
     group members
     %small
       (#{members.count})
-    .panel-head-actions
-      = link_to group_group_members_path(@group), class: 'btn btn-sm' do
-        %i.fa.fa-pencil-square-o
-        Edit group members
-  %ul.well-list
-    - members.each do |member|
+    - if can?(current_user, :admin_group_member, @group)
+      .pull-right
+        = link_to group_group_members_path(@group), class: 'btn' do
+          = icon('pencil-square-o')
+          Manage group members
+  %ul.content-list
+    - members.limit(20).each do |member|
       = render 'groups/group_members/group_member', member: member, show_controls: false
     - if members.count > 20
       %li
diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml
index f07cd97e63d45316913e2d4a981da4ba648dc367..05bf3a7ef6a12ee2a2265ba519ea32b4e636e9f3 100644
--- a/app/views/projects/project_members/_project_member.html.haml
+++ b/app/views/projects/project_members/_project_member.html.haml
@@ -4,7 +4,7 @@
 %li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)}
   %span.list-item-name
     - if member.user
-      = image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
+      = image_tag avatar_icon(user, 24), class: "avatar s24", alt: ''
       %strong
         = link_to user.name, user_path(user)
       %span.cgray= user.username
@@ -14,7 +14,7 @@
         %label.label.label-danger
           %strong Blocked
     - else
-      = image_tag avatar_icon(member.invite_email, 16), class: "avatar s16", alt: ''
+      = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: ''
       %strong
         = member.invite_email
       %span.cgray
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index b807fb2cc9dffa65257ec981a93d968436762175..ccddab13aafb82e5f6d932be48d9f6525158a8fa 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -1,9 +1,21 @@
-.panel.panel-default.prepend-top-20
+.panel.panel-default
   .panel-heading
     %strong #{@project.name}
     project members
     %small
       (#{members.count})
-  %ul.well-list
+    .pull-right
+      = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form'  do
+        .form-group
+          = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
+        = button_tag class: 'btn', title: 'Search' do
+          = icon("search")
+  %ul.content-list
     - members.each do |project_member|
       = render 'project_member', member: project_member
+
+:javascript
+  $('form.member-search-form').on('submit', function (event) {
+    event.preventDefault();
+    Turbolinks.visit(this.action + '?' + $(this).serialize());
+  });
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 9fc4be583cc3fa6740a9a66a8d9579ee5f96826b..29225a3636455d35c8727c51ab0ef86e0eb6660a 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,36 +1,21 @@
 - page_title "Members"
 = render "header_title"
+- @blank_container = true
 
-.gray-content-block.top-block
-  .clearfix.js-toggle-container
-    = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form'  do
-      .form-group
-        = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false }
-      = button_tag 'Search', class: 'btn'
-
-    - if can?(current_user, :admin_project_member, @project)
-      %span.pull-right
-        = button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do
-          Add members
-          %i.fa.fa-chevron-down
-        = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
-          Import members
-
-      .js-toggle-content.hide.new-group-member-holder
+.project-members-page
+  - if can?(current_user, :admin_project_member, @project)
+    .panel.panel-default
+      .panel-heading
+        Add new user to project
+        .pull-right
+          = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
+            Import members
+      .panel-body
+        %p.light
+          Users with access to this project are listed below.
         = render "new_project_member"
 
-%p.prepend-top-default.light
-  Users with access to this project are listed below.
-  Read more about project permissions
-  %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
-
-= render "team", members: @project_members
-
-- if @group
-  = render "group_members", members: @group_members
+  = render "team", members: @project_members
 
-:javascript
-  $('form.member-search-form').on('submit', function (event) {
-    event.preventDefault();
-    Turbolinks.visit(this.action + '?' + $(this).serialize());
-  });
+  - if @group
+    = render "group_members", members: @group_members
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 52b3a50c1e61f41913f11fc15d0f44be6a5d28b9..cfd7e1534ca81c1d24778fa72787f9a3b7353e99 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -3,7 +3,7 @@
 %p.light Keep stable branches secure and force developers to use Merge Requests
 %hr
 
-.well.append-bottom-20
+.well
   %p Protected branches are designed to
   %ul
     %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
@@ -22,7 +22,7 @@
     .form-group
       = f.label :name, "Branch", class: 'control-label'
       .col-sm-10
-        = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"})
+        = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
     .form-group
       .col-sm-offset-2.col-sm-10
         .checkbox
@@ -33,4 +33,3 @@
     .form-actions
       = f.submit 'Protect', class: "btn-create btn"
 = render 'branches_list'
-
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index f516b65ecd0512fd131f6c06a39cd58ff0488ea9..bc80f2f29adb4491574f3b452a32f70e841c6d36 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -14,6 +14,6 @@
       = render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit form-control'
       = render 'projects/notes/hints'
       .error-alert
-      .prepend-top-default
+      .form-actions.prepend-top-default
         = f.submit 'Save changes', class: 'btn btn-save'
         = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
index 07c24950ee238e89c7d5744d79ea54bd79d5db6d..b9486a9b49265c92771b58c529b26fd9f51cb4de 100644
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ b/app/views/projects/repositories/_download_archive.html.haml
@@ -3,10 +3,10 @@
 - split_button = split_button || false
 - if split_button == true
   %span.btn-group{class: btn_class}
-    = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn btn-success col-xs-10', rel: 'nofollow' do
+    = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do
       %i.fa.fa-download
       %span Download zip
-    %a.col-xs-2.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown' }
+    %a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
       %span.caret
       %span.sr-only
         Select Archive Format
diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index f3526ad0747a8dbe31117f5e0dd7308efc9c3db1..6ca919f7f80292ad448a95b606e63a783a88fbc8 100644
--- a/app/views/projects/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -12,7 +12,7 @@
       = link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
         %code= commit.short_id
       = image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
-      = gfm escape_once(truncate(commit.title, length: 40))
+      = markdown escape_once(truncate(commit.title, length: 40)), pipeline: :single_line
   %td
     %span.pull-right.cgray
       = time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index e6b8a2e6fe76b77cd62056e70ea0ad2c6c5ec858..47ec420189dc78eb73d140b70cc6544f970c3738 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -15,10 +15,10 @@
         - if runner.belongs_to_one_project?
           = link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
         - else
-          - runner_project = @ci_project.runner_projects.find_by(runner_id: runner)
-          = link_to 'Disable for this project', [:ci, @ci_project, runner_project], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
+          - runner_project = @project.runner_projects.find_by(runner_id: runner)
+          = link_to 'Disable for this project', namespace_project_runner_project_path(@project.namespace, @project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
       - elsif runner.specific?
-        = form_for [:ci, @ci_project, @ci_project.runner_projects.new] do |f|
+        = form_for [@project.namespace.becomes(Namespace), @project, @project.runner_projects.new] do |f|
           = f.hidden_field :runner_id, value: runner.id
           = f.submit 'Enable for this project', class: 'btn btn-sm'
   .pull-right
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index 316ea747b147211dbb59281f7bac65d197514707..6a37f444bb7bef9c73cf8ed118292b2304f34147 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -3,17 +3,17 @@
 .bs-callout.bs-callout-warning
   GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X.
   %hr
-  - if @ci_project.shared_runners_enabled
-    = link_to toggle_shared_runners_ci_project_path(@ci_project), class: 'btn btn-warning', method: :post do
+  - if @project.shared_runners_enabled?
+    = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do
       Disable shared runners
   - else
-    = link_to toggle_shared_runners_ci_project_path(@ci_project), class: 'btn btn-success', method: :post do
+    = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-success', method: :post do
       Enable shared runners
   &nbsp; for this project
 
 - if @shared_runners_count.zero?
-  This application has no shared runners yet.
-  Please use specific runners or ask administrator to create one
+  This GitLab server does not provide any shared runners yet.
+  Please use specific runners or ask the administrator to create one.
 - else
   %h4.underlined-title Available shared runners - #{@shared_runners_count}
   %ul.bordered-list.available-shared-runners
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index c13625c7e49abadf337d39ca5a0ecf96fba7940c..30cd1263a12235a77573f78c6a96bccd1036ebf4 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -12,7 +12,7 @@
       %code #{ci_root_url(only_path: false)}
     %li
       Use the following registration token during setup:
-      %code #{@ci_project.token}
+      %code #{@project.runners_token}
     %li
       Start runner!
 
diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml
index dde9e448cb92194e2c7efd70c4914712c3c59d21..eba03028af8c34647372f3dedde29044a0ba4011 100644
--- a/app/views/projects/runners/edit.html.haml
+++ b/app/views/projects/runners/edit.html.haml
@@ -23,7 +23,7 @@
     = label_tag :tag_list, class: 'control-label' do
       Tags
     .col-sm-10
-      = f.text_field :tag_list, class: 'form-control'
+      = f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control'
       .help-block You can setup jobs to only use runners with specific tags
   .form-actions
-    = f.submit 'Save', class: 'btn btn-save'
+    = f.submit 'Save changes', class: 'btn btn-save'
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index e1823b5119849c36232074f9813e791e61c5ccef..1b70880043a85f167188d6fdcf496b493416b707 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -4,18 +4,15 @@
 
 %p= @service.description
 
-.back-link
-  = link_to namespace_project_services_path(@project.namespace, @project) do
-    &larr; to services
-
 %hr
 
 = 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-actions
-    = form.submit 'Save', class: 'btn btn-save'
+    = form.submit 'Save changes', class: 'btn btn-save'
     &nbsp;
     - if @service.valid? && @service.activated?
       - disabled = @service.can_test? ? '':'disabled'
       = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service.to_param), class: "btn #{disabled}"
+    = link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 585caf674c961982e849ec5fad5dd7bf4e27529b..7466a098e24d0ba1e735d307ffc41c17c7a458b2 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -11,7 +11,7 @@
 
 = render "home_panel"
 
-.project-stats.gray-content-block
+.project-stats.gray-content-block.second-block
   %ul.nav.nav-pills
     %li
       = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
@@ -71,7 +71,7 @@
   = render default_project_view
 
 - if current_user
-  - access = user_max_access_in_project(current_user, @project)
+  - access = user_max_access_in_project(current_user.id, @project)
   - if access
     .prepend-top-20.project-footer
       .gray-content-block.footer-block.center
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index e69f2d99709ea65d2abc782e52b1f773e53c4c2d..dc3ea1fcf126877f1cacb03974240525c81e002a 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -2,6 +2,6 @@
 = render "header_title"
 
 %h3.page-title
-  Edit snippet
+  Edit Snippet
 %hr
 = render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet), visibility_level: @snippet.visibility_level
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index 67cd69fd215fd06a3af12fa50a93d5d1416f3a30..e57237991b44588ba4ba9ec45ad69e9f8ab7e1c9 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -2,6 +2,6 @@
 = render "header_title"
 
 %h3.page-title
-  New snippet
+  New Snippet
 %hr
 = render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet), visibility_level: default_snippet_visibility
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index 5d706942f2d4cd57080a1067975c39aad0fa9b66..7c599563ce45130f1e76cd7c7f5e8a32c119f83a 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -10,8 +10,8 @@
       %strong
         = @snippet.file_name
       .file-actions.hidden-xs
-        .btn-group.tree-btn-group
-          = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
+        = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
+        = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
 
     = render 'shared/snippets/blob'
 
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index e2c5178185e475e03e4d2f8b61e977420b831b77..28b706c5c7e85f95f928229d0ca5944b1005dffd 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -11,11 +11,17 @@
       = strip_gpg_signature(tag.message)
 
     .controls
-      = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do
-        = icon("pencil")
-      - if can? current_user, :download_code, @project
+      - if can?(current_user, :download_code, @project)
         = render 'projects/tags/download', ref: tag.name, project: @project
 
+      - if can?(current_user, :push_code, @project)
+        = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn has_tooltip', title: "Edit release notes" do
+          = icon("pencil")
+
+      - if can?(current_user, :admin_project, @project)
+        = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
+          = icon("trash-o")
+
   - if commit
     = render 'projects/branches/commit', commit: commit, project: @project
   - else
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 85d76eae3b518e34b16c9777c98485e0911cc40b..760347de0a9943a853c4e23770b3a3e305580779 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -6,7 +6,7 @@
   - if can? current_user, :push_code, @project
     .pull-right
       = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
-        %i.fa.fa-add-sign
+        = icon('plus')
         New tag
   .oneline
     Tags give the ability to mark specific points in history as being important
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 86aa15dc5b3e209bbcd2a8243f1cc140a493a3f6..3a2f75fecaa38f4bf8923fc4aae758276a662a2c 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -7,24 +7,24 @@
     = @error
 
 %h3.page-title
-  New git tag
+  New Tag
 %hr
 
-= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form" do
+= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-requires-input" do
   .form-group
-    = label_tag :tag_name, 'Name for new tag', class: 'control-label'
+    = label_tag :tag_name, nil, class: 'control-label'
     .col-sm-10
-      = text_field_tag :tag_name, params[:tag_name], placeholder: 'v3.0.1', required: true, tabindex: 1, class: 'form-control'
+      = text_field_tag :tag_name, params[:tag_name], required: true, tabindex: 1, autofocus: true, class: 'form-control'
   .form-group
     = label_tag :ref, 'Create from', class: 'control-label'
     .col-sm-10
-      = text_field_tag :ref, params[:ref], placeholder: 'master', required: true, tabindex: 2, class: 'form-control'
+      = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
       .help-block  Branch name or commit SHA
   .form-group
-    = label_tag :message, 'Message', class: 'control-label'
+    = label_tag :message, nil, class: 'control-label'
     .col-sm-10
-      = text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control'
-      .help-block (Optional) Entering a message will create an annotated tag.
+      = text_field_tag :message, nil, required: false, tabindex: 3, class: 'form-control'
+      .help-block Optionally, enter a message to create an annotated tag.
   %hr
   .form-group
     = label_tag :release_description, 'Release notes', class: 'control-label'
@@ -32,16 +32,15 @@
       = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
         = render 'projects/zen', attr: :release_description, classes: 'description js-quick-submit form-control'
         = render 'projects/notes/hints'
-        .help-block (Optional) You can add release notes to your tag. It will be stored in the GitLab database and shown on the tags page
+        .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
   .form-actions
     = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
     = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
 
 :javascript
-  disableButtonIfAnyEmptyField($("#new-tag-form"), ".form-control", ".btn-create");
-  var availableTags = #{@project.repository.ref_names.to_json};
+  var availableRefs = #{@project.repository.ref_names.to_json};
 
   $("#ref").autocomplete({
-    source: availableTags,
+    source: availableRefs,
     minLength: 1
   });
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 879c6c7d310bccd5ebd76d3a744d0c6c0b09dfe2..b594d4f1f27674e40232a9f8ac46914b8c8e81a3 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -5,17 +5,17 @@
 .gray-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', title: 'Edit release notes' do
+      = 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', title: 'Browse source code' do
+    = 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', title: 'Browse commits' do
+    = 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', method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
+        = 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
     %strong= @tag.name
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index c64e684df262f25c8777c8b082e1cce5fc035ba4..1927883513afbf2a2e0b96e8ea1973a97da658fe 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -12,7 +12,7 @@
               %i.fa.fa-angle-right
               &nbsp;
               %small.light
-                = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit)
+                = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
                 &ndash;
                 = truncate(@commit.title, length: 50)
             = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right'
@@ -29,7 +29,7 @@
   - if tree.readme
     = render "projects/tree/readme", readme: tree.readme
 
-- if allowed_tree_edit?
+- if can_edit_tree?
   = render 'projects/blob/upload', title: 'Upload New File', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
   = render 'projects/blob/new_dir'
 
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 1115ca6b4caaf333d997f4eff4efea2243fc218c..3343288ad2b1da800a7ce50eca1176ea2c19ce9d 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -11,22 +11,65 @@
         = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
       - else
         = link_to title, '#'
-  - if allowed_tree_edit?
+
+  - if current_user
     %li
-      %span.dropdown
-        %a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
+      - if !on_top_of_branch?
+        %span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }}
           = icon('plus')
-        %ul.dropdown-menu
-          %li
-            = link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do
-              = icon('pencil fw')
-              Create file
-          %li
-            = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
-              = icon('file fw')
-              Upload file
-          %li.divider
-          %li
-            = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
-              = icon('folder fw')
-              New directory
+      - else
+        %span.dropdown
+          %a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"}
+            = icon('plus')
+          %ul.dropdown-menu
+            - if can_edit_tree?
+              %li
+                = link_to namespace_project_new_blob_path(@project.namespace, @project, @id) do
+                  = icon('pencil fw')
+                  New file
+              %li
+                = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
+                  = icon('file fw')
+                  Upload file
+              %li
+                = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
+                  = icon('folder fw')
+                  New directory
+            - elsif can?(current_user, :fork_project, @project)
+              %li
+                - continue_params = { to:         namespace_project_new_blob_path(@project.namespace, @project, @id),
+                                      notice:     edit_in_new_fork_notice,
+                                      notice_now: edit_in_new_fork_notice_now }
+                - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key:  current_user.namespace.id,
+                                                                                        continue:       continue_params)
+                = link_to fork_path, method: :post do
+                  = icon('pencil fw')
+                  New file
+              %li
+                - continue_params = { to:         request.fullpath,
+                                      notice:     edit_in_new_fork_notice + " Try to upload a file again.",
+                                      notice_now: edit_in_new_fork_notice_now }
+                - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key:  current_user.namespace.id,
+                                                                                        continue:       continue_params)
+                = link_to fork_path, method: :post do
+                  = icon('file fw')
+                  Upload file
+              %li
+                - continue_params = { to:         request.fullpath,
+                                      notice:     edit_in_new_fork_notice + " Try to create a new directory again.",
+                                      notice_now: edit_in_new_fork_notice_now }
+                - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key:  current_user.namespace.id,
+                                                                                        continue:       continue_params)
+                = link_to fork_path, method: :post do
+                  = icon('folder fw')
+                  New directory
+
+            %li.divider
+            %li
+              = link_to new_namespace_project_branch_path(@project.namespace, @project) do
+                = icon('code-fork fw')
+                New branch
+            %li
+              = link_to new_namespace_project_tag_path(@project.namespace, @project) do
+                = icon('tags fw')
+                New tag
diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml
index b3ad79a200ed43648229f15f0381d779ead3f742..bd346c4b8e6f247e63897829b37af2b1f44ed59c 100644
--- a/app/views/projects/triggers/index.html.haml
+++ b/app/views/projects/triggers/index.html.haml
@@ -36,7 +36,8 @@
     :plain
       curl -X POST \
            -F token=TOKEN \
-           #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}
+           -F ref=REF_NAME \
+           #{builds_trigger_url(@project.id)}
   %h3
     Use .gitlab-ci.yml
 
@@ -51,7 +52,7 @@
       trigger:
         type: deploy
         script:
-          - "curl -X POST -F token=TOKEN #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}"
+          - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
   %h3
     Pass build variables
 
@@ -65,5 +66,6 @@
     :plain
       curl -X POST \
            -F token=TOKEN \
+           -F "ref=REF_NAME" \
            -F "variables[RUN_NIGHTLY_BUILD]=true" \
-           #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}
+           #{builds_trigger_url(@project.id)}
diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml
index e052da1ac4356ba85aa6bc98e14660b3536bed9c..e80dffc1ced3360aa26e8b58e08d9916fe3f4697 100644
--- a/app/views/projects/variables/show.html.haml
+++ b/app/views/projects/variables/show.html.haml
@@ -10,13 +10,13 @@
 %hr
 
 
-= nested_form_for @ci_project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' }  do |f|
+= nested_form_for @project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' }  do |f|
   - if @project.errors.any?
     #error_explanation
-      %p.lead= "#{pluralize(@ci_project.errors.count, "error")} prohibited this project from being saved:"
+      %p.lead= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:"
       .alert.alert-error
         %ul
-          - @ci_project.errors.full_messages.each do |msg|
+          - @project.errors.full_messages.each do |msg|
             %li= msg
 
   = f.fields_for :variables do |variable_form|
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 9c94c43e747e776d135344a45dbc1746803ee42b..1d257818dcdbe03fb15a1524b808ec65f83a0431 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default' } do |f|
   -if @page.errors.any?
     #error_explanation
       .alert.alert-danger
@@ -11,14 +11,7 @@
     .col-sm-10
       = f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control"
 
-  .row
-    .col-sm-offset-2.col-sm-10
-      %p.cgray
-        To link to a (new) page you can just type
-        %code [Link Title](page-slug)
-        \.
-
-  .form-group.wiki-content
+  .form-group
     = f.label :content, class: 'control-label'
     .col-sm-10
       = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
@@ -27,6 +20,11 @@
 
       .clearfix
       .error-alert
+
+      .help-block
+        To link to a (new) page, simply type
+        %code [Link Title](page-slug)
+        \.
   .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/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 14f25822259ccea685036107f600b24b5749b0bb..29bf5d62abed4605ede417c6839eba190f98c0e2 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,9 +1,4 @@
 %span.pull-right
-  - if can?(current_user, :create_wiki, @project)
-    = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new btn-grouped", "data-toggle" => "modal" do
-      %i.fa.fa-plus
-      New Page
-
   - if (@page && @page.persisted?)
     = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
       Page History
@@ -11,5 +6,7 @@
       = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
         %i.fa.fa-pencil-square-o
         Edit
-
-= render 'projects/wikis/new'
+    - if can?(current_user, :admin_wiki, @project)
+      = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-remove" do
+        = icon('trash')
+        Delete
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index fffb4eb31aba3c4be3714632364c4c7ba69d9ee2..e6e6ad5bc4b631473f8970d19b05b2cd70f8d85c 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,10 +1,19 @@
-%ul.center-top-menu
-  = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
-    = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
+.project-issuable-filter
+  .controls
+    - if can?(current_user, :create_wiki, @project)
+      = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
+        %i.fa.fa-plus
+        New Page
 
-  = nav_link(path: 'wikis#pages') do
-    = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
+      = render 'projects/wikis/new'
 
-  = nav_link(path: 'wikis#git_access') do
-    = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
-      Git Access
+  %ul.center-top-menu
+    = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
+      = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
+
+    = nav_link(path: 'wikis#pages') do
+      = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
+
+    = nav_link(path: 'wikis#git_access') do
+      = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
+        Git Access
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index dace172438cd6268ccef2a6c13fca80e6f3b28d1..f0547e9c0575d7259e853b44949b440d58d84604 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -12,5 +12,5 @@
           The page slug is invalid. Please don't use characters other then: a-z 0-9 _ - and /
         %p.hint
           Please don't use spaces.
-      .modal-footer
-        = link_to 'Build', '#', class: 'build-new-wiki btn btn-create'
+        .form-actions
+          = link_to 'Create Page', '#', class: 'build-new-wiki btn btn-create'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 0b709c3695b8f908584bc1d1837a4a21a33d35da..23f64fbbd1062d33ccac23854bd874dcf4072d2e 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,16 +1,16 @@
-- page_title "Edit", @page.title, "Wiki"
+- page_title "Edit", @page.title.capitalize, "Wiki"
 = render "header_title"
 
 = render 'nav'
-.pull-right
-  = render 'main_links'
-%h3.page-title
-  Editing -
-  %span.light #{@page.title}
-%hr
-= render 'form'
+.gray-content-block
+  .pull-right
+    = render 'main_links'
+
+  %h3.page-title.oneline
+    %span.light Edit Page
+    - if @page.persisted?
+      = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
+    - else
+      = @page.title
 
-.pull-right
-  - if @page.persisted? && can?(current_user, :admin_wiki, @project)
-    = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-sm btn-remove" do
-      Delete this page
+= render 'form'
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index 6417ef4a38bae363a077aae039b70e2d3f67a8e7..11c8c4f0eba81ae1273a88880999d332c314719b 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -5,7 +5,7 @@
 .gray-content-block
   .row
     .col-sm-6
-      %h3.page-title
+      %h3.page-title.oneline
         Git access for
         %strong= @project_wiki.path_with_namespace
 
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index d179a1abec1323380c4de50cc3cdb40f7a1f46da..aae1ad69ad90c3272cbd98418129c3d0707b11db 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,11 +1,10 @@
-- page_title "All Pages", "Wiki"
+- page_title "Pages", "Wiki"
 = render "header_title"
 
 = render 'nav'
 .gray-content-block
-  = render 'main_links'
-  %h3.page-title
-    All Pages
+  All pages in this wiki are listed below.
+  
 %ul.content-list
   - @wiki_pages.each do |wiki_page|
     %li
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 55fbf5a8b6e27f995c79652b4a5c1d98f8190549..309d40f52bc6832ddb9919a9faf591eb7da02bbf 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -5,11 +5,12 @@
 
 .gray-content-block
   = render 'main_links'
-  %h3.page-title
+  %h3.page-title.oneline
     = @page.title.capitalize
 
-  .wiki-last-edit-by
-    Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+    %span.wiki-last-edit-by
+      &middot;
+      last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
 
 - if @page.historical?
   .warning_message
@@ -21,8 +22,3 @@
   .wiki
     = preserve do
       = render_wiki_content(@page)
-
-.gray-content-block.footer-block
-  .wiki-last-edit-by
-    Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
-
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index ce8ddff955690ea4895580cdb23945e75fd8bd7d..45d700781f3e3d14547268ef9444810112a8e32e 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -6,7 +6,7 @@
   - if issue.description.present?
     .description.term
       = preserve do
-        = search_md_sanitize(markdown(issue.description))
+        = search_md_sanitize(markdown(issue.description, { project: issue.project }))
   %span.light
     #{issue.project.name_with_namespace}
   - if issue.closed?
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 8bcb24ae9dff6a683dcc3a16416be9b9049bb38d..687a59c270f9f35d5b940aa740ac60b18f453839 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -1,26 +1,27 @@
 - project = project || @project
-.git-clone-holder.input-group
-  .input-group-addon.git-protocols
-    .input-group-btn
-      %button{ |
-        type: 'button', |
-        class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", |
-        :"data-clone" => project.ssh_url_to_repo, |
-        :"data-title" => "Add an SSH key to your profile<br> to pull or push via SSH.",
-        :"data-html" => "true",
-        :"data-container" => "body"}
-        SSH
-    .input-group-btn
-      %button{ |
-        type: 'button', |
-        class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", |
-        :"data-clone" => project.http_url_to_repo, |
-        :"data-title" => "Set a password on your account<br> to pull or push via #{gitlab_config.protocol.upcase}.",
-        :"data-html" => "true",
-        :"data-container" => "body"}
-        = gitlab_config.protocol.upcase
+
+.git-clone-holder
+  .btn-group.clone-options
+    %a#clone-dropdown.clone-dropdown-btn.btn{href: '#', 'data-toggle' => 'dropdown'}
+      %span
+        = default_clone_protocol.upcase
+      = icon('angle-down')
+    %ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown
+      %li
+        %a#ssh-selector{href: @project.ssh_url_to_repo}
+          SSH
+      %li
+        %a#http-selector{href: @project.http_url_to_repo}
+          HTTPS
+
   = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
-  - if project.kind_of?(Project)
-    .input-group-addon.has_tooltip{title: "#{visibility_level_label(project.visibility_level)} project", data: { container: "body" } }
-      .visibility-level-label
-        = visibility_level_icon(project.visibility_level)
+  .input-group-btn
+    = clipboard_button(clipboard_target: '#project_clone')
+
+:javascript
+  $('ul.clone-options-dropdown a').on('click',function(e){
+    e.preventDefault();
+    var $this = $(this);
+    $('a.clone-dropdown-btn span').text($this.text());
+    $('#project_clone').val($this.attr('href'));
+  });
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 2a44817e05a8a668e068c032949af3326eb4995a..34241cd8aad353d83a570e2b73a858164586ac15 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -3,7 +3,8 @@
     .modal-content
       .modal-header
         %a.close{href: "#", "data-dismiss" => "modal"} ×
-        %h4 Confirmation required
+        %h3.page-title
+          Confirmation required
 
       .modal-body
         %p.cred.lead.js-confirm-text
@@ -18,5 +19,5 @@
 
         .form-group
           = text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
-        .form-group
+        .form-actions
           = submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit"
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 57c3aff3e1892480a7f6d1906d5d85f26def4733..2bc98983d672d2f7e91cdadfeb7b9325852e5871 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -8,5 +8,6 @@
         %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
           %i.fa.fa-link
           = i
-  :preserve
-    #{highlight(blob.name, blob.data)}
+  .blob-content{data: {blob_id: blob.id}}
+    :preserve
+      #{highlight(blob.name, blob.data)}
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index c0a9923348e72b49d8d1bd6d18f177fd06d2ea5b..67072b9fc2abb513da29f06315bab8b921ec4edd 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -23,7 +23,7 @@
           %li It will change the git path to repositories under this group.
 
 .form-group.group-description-holder
-  = f.label :description, 'Details', class: 'control-label'
+  = f.label :description, class: 'control-label'
   .col-sm-10
     = f.text_area :description, maxlength: 250,
         class: 'form-control js-gfm-input', rows: 4
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 0dbb6a04393720ada519a3b81d158f1a6e7f6b27..4b4c9e9eabe20248a893f855cbada00e0bb11327 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -3,8 +3,10 @@
     .panel.panel-default.panel-small
       - project = group[0]
       .panel-heading
-        = link_to_project project
-        = link_to 'show all', namespace_project_issues_path(project.namespace, project), class: 'pull-right'
+        = link_to project.name_with_namespace, namespace_project_issues_path(project.namespace, project)
+        - if can?(current_user, :create_issue, project)
+          .pull-right
+            = link_to 'New issue', new_namespace_project_issue_path(project.namespace, project)
 
       %ul.well-list.issues-list
         - group[1].each do |issue|
@@ -12,4 +14,3 @@
   = paginate @issues, theme: "gitlab"
 - else
   .nothing-here-block No issues to show
-
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index c02c5af008a35deffb3599f5c40ac886827e6744..be17a511b26c7ec0fef762d433d8667115b99d16 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -3,8 +3,11 @@
     .panel.panel-default.panel-small
       - project = group[0]
       .panel-heading
-        = link_to_project project
-        = link_to 'show all', namespace_project_merge_requests_path(project.namespace, project), class: 'pull-right'
+        = link_to project.name_with_namespace, namespace_project_merge_requests_path(project.namespace, project)
+        - if can?(current_user, :create_merge_request, project)
+          .pull-right
+            = link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project)
+
       %ul.well-list.mr-list
         - group[1].each do |merge_request|
           = render 'projects/merge_requests/merge_request', merge_request: merge_request
diff --git a/app/views/shared/_milestone_expired.html.haml b/app/views/shared/_milestone_expired.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b8eef15fbec4e37ddea3ef780a261a21060f7347
--- /dev/null
+++ b/app/views/shared/_milestone_expired.html.haml
@@ -0,0 +1,5 @@
+- if milestone.expired? and not milestone.closed?
+  %span.cred (Expired)
+- if milestone.expires_at
+  %span
+    = milestone.expires_at
diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml
index 8636341c60dbe99f177c5d0be368bb40de421ede..0c8ac48bb58f8d3ced26fbbf7d54f7bb58f61d83 100644
--- a/app/views/shared/_new_commit_form.html.haml
+++ b/app/views/shared/_new_commit_form.html.haml
@@ -1,18 +1,22 @@
 = render 'shared/commit_message_container', placeholder: placeholder
 
-- unless @project.empty_repo?
-  .form-group.branch
-    = label_tag 'branch', class: 'control-label' do
-      Branch
-    .col-sm-10
-      = text_field_tag 'new_branch', @new_branch || @ref, class: "form-control js-new-branch"
+- if @project.empty_repo?
+  = hidden_field_tag 'target_branch', @ref
+- else
+  - if can?(current_user, :push_code, @project)
+    .form-group.branch
+      = label_tag 'target_branch', 'Target branch', class: 'control-label'
+      .col-sm-10
+        = text_field_tag 'target_branch', @target_branch || tree_edit_branch, required: true, class: "form-control js-target-branch"
 
-  .form-group.js-create-merge-request-form-group
-    .col-sm-offset-2.col-sm-10
-      .checkbox
-        - nonce = SecureRandom.hex
-        = label_tag "create_merge_request-#{nonce}" do
-          = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
-          Start a <strong>new merge request</strong> with this commit
+        .js-create-merge-request-container
+          .checkbox
+            - nonce = SecureRandom.hex
+            = label_tag "create_merge_request-#{nonce}" do
+              = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
+              Start a <strong>new merge request</strong> with these changes
+  - else
+    = hidden_field_tag 'target_branch', @target_branch || tree_edit_branch
+    = hidden_field_tag 'create_merge_request', 1
 
   = hidden_field_tag 'original_branch', @ref, class: 'js-original-branch'
diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..c4431d66927eb999b7fd5958e6e69d118b450f57
--- /dev/null
+++ b/app/views/shared/_new_project_item_select.html.haml
@@ -0,0 +1,20 @@
+- if @projects.any?
+  .prepend-left-10.new-project-item-select-holder
+    = project_select_tag :project_path, class: "new-project-item-select", data: { include_groups: local_assigns[:include_groups] }
+    %a.btn.btn-new.new-project-item-select-button
+      = icon('plus')
+      = local_assigns[:label]
+      %b.caret
+
+  :javascript
+    $('.new-project-item-select-button').on('click', function() {
+      $('.new-project-item-select').select2('open');
+    });
+
+    var relativePath = '#{local_assigns[:path]}';
+
+    $('.new-project-item-select').on('click', function() {
+      window.location = $(this).val() + '/' + relativePath;
+    });
+
+    new ProjectSelect()
diff --git a/app/views/shared/_project_limit.html.haml b/app/views/shared/_project_limit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..960ff00b49d70b91411117deb589dd649f97292c
--- /dev/null
+++ b/app/views/shared/_project_limit.html.haml
@@ -0,0 +1,8 @@
+- if cookies[:hide_project_limit_message].blank? && !current_user.hide_project_limit && !current_user.can_create_project?
+  .project-limit-message.alert.alert-warning.hidden-xs
+    You won't be able to create new projects because you have reached your project limit.
+
+    .pull-right
+      = link_to "Don't show again", profile_path(user: {hide_project_limit: true}), method: :put, class: 'alert-link'
+      |
+      = link_to 'Remind later', '#', class: 'hide-project-limit-message alert-link'
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index 16a98a7233ce57842b9b4ded3d8eb17cf21fb08e..28d6f421fea8076eba34f100cfef54c9f218ea3e 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -59,6 +59,15 @@
               %strong Merge Request events
             %p.light
               This url will be triggered when a merge request is created
+      - 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
+
 
 - @service.fields.each do |field|
   - type = field[:type]
diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml
deleted file mode 100644
index be66256c7b052d3ed05273afab50fa6faadd7922..0000000000000000000000000000000000000000
--- a/app/views/shared/issuable/_context.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
-  %div.prepend-top-20
-    .issuable-context-title
-      %label
-        Assignee:
-      - if issuable.assignee
-        %strong= link_to_member(@project, issuable.assignee, size: 24)
-      - else
-        none
-    .issuable-context-selectbox
-      - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
-        = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true)
-
-  %div.prepend-top-20.clearfix
-    .issuable-context-title
-      %label
-        Milestone:
-      - if issuable.milestone
-        %span.back-to-milestone
-          = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
-            %strong
-              = icon('clock-o')
-              = issuable.milestone.title
-      - else
-        none
-    .issuable-context-selectbox
-      - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
-        = f.select(:milestone_id, milestone_options(issuable), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'})
-        = hidden_field_tag :issuable_context
-        = f.submit class: 'btn hide'
-
-  - if current_user
-    - subscribed = issuable.subscribed?(current_user)
-    %div.prepend-top-20.clearfix
-      .issuable-context-title
-        %label
-          Subscription:
-      %button.btn.btn-block.subscribe-button{:type => 'button'}
-        = icon('eye')
-        %span= subscribed ? 'Unsubscribe' : 'Subscribe'
-      - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
-      .subscription-status{data: {status: subscribtion_status}}
-        .description-block.unsubscribed{class: ( 'hidden' if subscribed )}
-          You're not receiving notifications from this thread.
-        .description-block.subscribed{class: ( 'hidden' unless subscribed )}
-          You're receiving notifications because you're subscribed to this thread.
-
-:javascript
-  new Subscription("#{toggle_subscription_path(issuable)}");
-  new IssuableContext();
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index d1231438ee4df03c2f04743dbce48957cdb737be..ac6c248ccf1df9f6e6b67dff7ce1ecb157f60b16 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -31,11 +31,11 @@
       .issues-other-filters
         .filter-item.inline
           = users_select_tag(:assignee_id, selected: params[:assignee_id],
-            placeholder: 'Assignee', class: 'trigger-submit', any_user: true, null_user: true, first_user: true, current_user: true)
+            placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true)
 
         .filter-item.inline
           = users_select_tag(:author_id, selected: params[:author_id],
-            placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true, current_user: true)
+            placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
 
         .filter-item.inline.milestone-filter
           = select_tag('milestone_title', projects_milestones_options,
@@ -53,12 +53,16 @@
     - if controller.controller_name == 'issues'
       .issues_bulk_update.hide
         = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post  do
-          = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status", class: 'form-control')
-          = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, first_user: true, current_user: true)
-          = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
+          .filter-item.inline
+            = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), include_blank: true, data: { placeholder: "Status" })
+          .filter-item.inline
+            = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, first_user: true, current_user: true)
+          .filter-item.inline
+            = select_tag('update[milestone_id]', bulk_update_milestone_options, include_blank: true, data: { placeholder: "Milestone" })
           = hidden_field_tag 'update[issues_ids]', []
           = hidden_field_tag :state_event, params[:state_event]
-          = button_tag "Update issues", class: "btn update_selected_issues btn-save"
+          .filter-item.inline
+            = button_tag "Update issues", class: "btn update_selected_issues btn-save"
 
 :javascript
   new UsersSelect();
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 0fc74d7d2b162825a1e086d54da83811bca0390e..90dc00624818e2631f55a905b85a38be4fb74303 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -6,8 +6,7 @@
           %span= msg
           %br
 .form-group
-  = f.label :title, class: 'control-label' do
-    %strong= 'Title *'
+  = f.label :title, class: 'control-label'
   .col-sm-10
     = f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
         class: 'form-control pad js-gfm-input js-quick-submit', required: true
@@ -20,7 +19,7 @@
         - else
           Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a
           <strong>Work In Progress</strong> merge request from being merged before it's ready.
-.form-group.issuable-description
+.form-group.detail-page-description
   = f.label :description, 'Description', class: 'control-label'
   .col-sm-10
 
@@ -30,29 +29,25 @@
       = render 'projects/notes/hints'
       .clearfix
       .error-alert
-  %hr
 - if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
+  %hr
   .form-group
     .issue-assignee
-      = f.label :assignee_id, class: 'control-label' do
-        %i.fa.fa-user
-        Assign to
+      = f.label :assignee_id, "Assignee", class: 'control-label'
       .col-sm-10
         = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
-            placeholder: 'Select a user', class: 'custom-form-control', null_user: true,
+            placeholder: 'Select assignee', class: 'custom-form-control', null_user: true,
             selected: issuable.assignee_id, project: @target_project || @project,
-            first_user: true, current_user: true)
+            first_user: true, current_user: true, include_blank: true)
         &nbsp;
         = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
   .form-group
     .issue-milestone
-      = f.label :milestone_id, class: 'control-label' do
-        %i.fa.fa-clock-o
-        Milestone
+      = f.label :milestone_id, "Milestone", class: 'control-label'
       .col-sm-10
         - if milestone_options(issuable).present?
           = f.select(:milestone_id, milestone_options(issuable),
-            { include_blank: 'Select milestone' }, { class: 'select2' })
+            { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } })
         - else
           .prepend-top-10
           %span.light No open milestones available.
@@ -60,13 +55,11 @@
         - if can? current_user, :admin_milestone, issuable.project
           = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
   .form-group
-    = f.label :label_ids, class: 'control-label' do
-      %i.fa.fa-tag
-      Labels
+    = f.label :label_ids, "Labels", class: 'control-label'
     .col-sm-10
       - if issuable.project.labels.any?
         = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
-          { selected: issuable.label_ids }, multiple: true, class: 'select2'
+          { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
       - else
         .prepend-top-10
         %span.light No labels yet.
@@ -78,31 +71,30 @@
   %hr
     - if @merge_request.new_record?
       .form-group
-        = f.label :source_branch, class: 'control-label' do
-          %i.fa.fa-code-fork
-          Source Branch
+        = f.label :source_branch, class: 'control-label'
         .col-sm-10
           = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
   .form-group
-    = f.label :target_branch, class: 'control-label' do
-      %i.fa.fa-code-fork
-      Target Branch
+    = f.label :target_branch, class: 'control-label'
     .col-sm-10
-      = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record? })
+      = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} })
       - if @merge_request.new_record?
         %p.help-block
         = link_to 'Change branches', mr_change_branches_path(@merge_request)
 
-.form-actions
-  - if !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project)) && !issuable.persisted?
-    %p
-      Please review the
-      %strong #{link_to 'guidelines for contribution', guide_url}
-      to this repository.
+- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
+.gray-content-block{class: (is_footer ? "footer-block" : "middle-block")}
   - if issuable.new_record?
-    = f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
+    = f.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
   - else
     = f.submit 'Save changes', class: 'btn btn-save'
+
+  - if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project))
+    .inline.prepend-left-10
+      Please review the
+      %strong #{link_to 'contribution guidelines', guide_url}
+      for this project.
+
   - if issuable.new_record?
     - cancel_project = issuable.source_project
   - else
diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..da6bacbb74a59dd4b5f61d5ca519694f01db38f4
--- /dev/null
+++ b/app/views/shared/issuable/_participants.html.haml
@@ -0,0 +1,5 @@
+.block.participants
+  .title
+    = pluralize participants.count, "participant"
+  - participants.each do |participant|
+    = link_to_member(@project, participant, name: false, size: 24)
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..79c5cc7f40aa27a1e7bde511f2aff57499a7f09e
--- /dev/null
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -0,0 +1,83 @@
+.issuable-sidebar.issuable-affix
+  = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
+    .block.assignee
+      .title
+        %label
+          Assignee
+        - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+          .pull-right
+            = link_to 'Edit', '#', class: 'edit-link'
+      .value
+        - if issuable.assignee
+          %strong= link_to_member(@project, issuable.assignee, size: 24)
+        - else
+          .light None
+
+      .selectbox
+        = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
+
+    .block.milestone
+      .title
+        %label
+          Milestone
+        - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+          .pull-right
+            = link_to 'Edit', '#', class: 'edit-link'
+      .value
+        - if issuable.milestone
+          %span.back-to-milestone
+            = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
+              %strong
+                = icon('clock-o')
+                = issuable.milestone.title
+        - else
+          .light None
+      .selectbox
+        = f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
+        = hidden_field_tag :issuable_context
+        = f.submit class: 'btn hide'
+
+    - if issuable.project.labels.any?
+      .block
+        .title
+          %label Labels
+          - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+            .pull-right
+              = link_to 'Edit', '#', class: 'edit-link'
+        .value.issuable-show-labels
+          - if issuable.labels.any?
+            - issuable.labels.each do |label|
+              = link_to_label(label)
+          - else
+            .light None
+        .selectbox
+          = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
+            { selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
+
+    .block
+      .title
+        Cross-project reference
+      .cross-project-reference
+        %span#cross-project-reference
+          = cross_project_reference(@project, issuable)
+        = clipboard_button(clipboard_target: 'span#cross-project-reference')
+
+    = render "shared/issuable/participants", participants: issuable.participants(current_user)
+
+    - if current_user
+      - subscribed = issuable.subscribed?(current_user)
+      .block.light
+        .title
+          %label.light Notifications
+        - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
+        %button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'}
+          %span= subscribed ? 'Unsubscribe' : 'Subscribe'
+        .subscription-status{data: {status: subscribtion_status}}
+          .unsubscribed{class: ( 'hidden' if subscribed )}
+            You're not receiving notifications from this thread.
+          .subscribed{class: ( 'hidden' unless subscribed )}
+            You're receiving notifications because you're subscribed to this thread.
+
+  :javascript
+    new Subscription("#{toggle_subscription_path(issuable)}");
+    new IssuableContext();
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 913b67448445ff966d4f397da0e3a31564388233..1041eccd1dfb0a08b160b2560535d0d348a20a85 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -1,5 +1,5 @@
 .snippet-form-holder
-  = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form" } do |f|
+  = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f|
     - if @snippet.errors.any?
       .alert.alert-danger
         %ul
@@ -8,7 +8,8 @@
 
     .form-group
       = f.label :title, class: 'control-label'
-      .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true
+      .col-sm-10
+        = f.text_field :title, class: 'form-control', required: true, autofocus: true
 
     = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet
 
@@ -27,7 +28,7 @@
       - if @snippet.new_record?
         = f.submit 'Create snippet', class: "btn-create btn"
       - else
-        = f.submit 'Save', class: "btn-save btn"
+        = f.submit 'Save changes', class: "btn-save btn"
 
       - if @snippet.project_id
         = link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 0a4a790ec5ee182b5ad0e5a62178dd0636246e45..aa5acee9c14c1e495826bdb6a9c0a58637691347 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -1,24 +1,25 @@
-.snippet-details
-  .page-title
-    .snippet-box{class: visibility_level_color(@snippet.visibility_level)}
-      = visibility_level_icon(@snippet.visibility_level)
-      = visibility_level_label(@snippet.visibility_level)
-    %span.snippet-id Snippet ##{@snippet.id}
-    %span.creator
-      &middot; created by #{link_to_member(@project, @snippet.author, size: 24)}
-      &middot;
-      = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
-      - if @snippet.updated_at != @snippet.created_at
-        %span
-          &middot;
-          = icon('edit', title: 'edited')
-          = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago')
+.detail-page-header
+  .snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: 'body' }}
+    = visibility_level_icon(@snippet.visibility_level, fw: false)
+    = visibility_level_label(@snippet.visibility_level)
+  %span.identifier
+    Snippet ##{@snippet.id}
+  %span.creator
+    &middot; created by #{link_to_member(@project, @snippet.author, size: 24)}
+    &middot;
+    = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
+    - if @snippet.updated_at != @snippet.created_at
+      %span
+        &middot;
+        = icon('edit', title: 'edited')
+        = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago')
 
-    .pull-right
-      - if @snippet.project_id?
-        = render "projects/snippets/actions"
-      - else
-        = render "snippets/actions"
-  .gray-content-block.middle-block
-    %h2.snippet-title
-      = gfm escape_once(@snippet.title)
+  .pull-right
+    - if @snippet.project_id?
+      = render "projects/snippets/actions"
+    - else
+      = render "snippets/actions"
+
+.detail-page-description.gray-content-block.second-block
+  %h2.title
+    = markdown escape_once(@snippet.title), pipeline: :single_line
diff --git a/app/views/sherlock/transactions/_general.html.haml b/app/views/sherlock/transactions/_general.html.haml
index 4287a0c32036f49cccd1b7ac1482186d7f1f5254..8533b130da684d588917991f92e67acd6715d087 100644
--- a/app/views/sherlock/transactions/_general.html.haml
+++ b/app/views/sherlock/transactions/_general.html.haml
@@ -25,6 +25,12 @@
         %strong
           = @transaction.duration.round(2)
           = t('sherlock.seconds')
+      %li
+        %span.light
+          #{t('sherlock.query_time')}
+        %strong
+          = @transaction.query_duration.round(2)
+          = t('sherlock.seconds')
       %li
         %span.light
           #{t('sherlock.finished_at')}:
diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml
index 1a380035661d4a24ca894e70734c13b8f8f7d096..82f44a9a5c3ba062497557c8bd75ee0f67b2d391 100644
--- a/app/views/snippets/edit.html.haml
+++ b/app/views/snippets/edit.html.haml
@@ -1,5 +1,5 @@
 - page_title "Edit", @snippet.title, "Snippets"
 %h3.page-title
-  Edit snippet
+  Edit Snippet
 %hr
 = render 'shared/snippets/form', url: snippet_path(@snippet), visibility_level: @snippet.visibility_level
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index a74d5e792ad428866f6125b6fcd8190b86a22a4e..79e2392490d5396cb581863b74da99e972e1c87c 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -1,5 +1,5 @@
 - page_title "New Snippet"
 %h3.page-title
-  New snippet
+  New Snippet
 %hr
 = render "shared/snippets/form", url: snippets_path(@snippet), visibility_level: default_snippet_visibility
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 69d8899d4c1d1467b2cb1e8f44261f62de2449f4..a2b365687700d5531210fffca18c35d178737bc8 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -9,6 +9,6 @@
       %strong
         = @snippet.file_name
       .file-actions.hidden-xs
-        .btn-group.tree-btn-group
-          = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
+        = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
+        = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
     = render 'shared/snippets/blob'
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index d5a92cb816adf83ea7e507a43a91ca97b41c855d..0bca8177e14e43f7fd892579a99612adbe19df58 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,5 +1,6 @@
-- page_title    @user.name
-- header_title  @user.name, user_path(@user)
+- page_title       @user.name
+- page_description @user.bio
+- header_title     @user.name, user_path(@user)
 
 = content_for :meta_tags do
   = auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
@@ -73,7 +74,7 @@
   .user-calendar-activities
 
 
-%ul.center-middle-menu
+%ul.center-top-menu.no-top.no-bottom.bottom-border.wide
   %li.active
     = link_to "#activity", 'data-toggle' => 'tab' do
       Activity
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 7eb27c12d3395028474f9cb969367e500be3f85b..ce0a0113403328f7c4d75b540fab4d746c03714f 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -1,32 +1,46 @@
 .awards.votes-block
-  - votable.notes.awards.grouped_awards.each do |emoji, notes|
+  - awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes|
     .award{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user)}
-      .icon{"data-emoji" => "#{emoji}"}
-        = image_tag url_to_emoji(emoji), height: "20px", width: "20px"
+      = emoji_icon(emoji)
       .counter
         = notes.count
 
   - if current_user
-    .dropdown.awards-controls
+    .awards-controls
       %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"}
         = icon('smile-o')
-      %ul.dropdown-menu.awards-menu
-        - emoji_list.each do |emoji|
-          %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px"
+      .emoji-menu
+        .emoji-menu-content
+          = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control"
+          - AwardEmoji.emoji_by_category.each do |category, emojis|
+            %h5= AwardEmoji::CATEGORIES[category]
+            %ul
+              - emojis.each do |emoji|
+                %li
+                  = emoji_icon(emoji["name"], emoji["unicode"], emoji["aliases"])
 
 - if current_user
   :coffeescript
     post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"
     noteable_type = "#{votable.class.name.underscore}"
     noteable_id = "#{votable.id}"
-    window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id)
+    aliases = #{AwardEmoji.aliases.to_json}
 
-    $(".awards-menu li").click (e)->
-      emoji = $(this).data("emoji")
+    window.awards_handler = new AwardsHandler(
+      post_emoji_url,
+      noteable_type,
+      noteable_id,
+      aliases
+    )
+
+    $(".awards").on "click", ".emoji-menu-content li", (e) ->
+      emoji = $(this).find(".emoji-icon").data("emoji")
       awards_handler.addAward(emoji)
 
-    $(".awards").on "click", ".award", (e)->
+    $(".awards").on "click", ".award", (e) ->
       emoji = $(this).find(".icon").data("emoji")
       awards_handler.addAward(emoji)
 
     $(".award").tooltip()
+
+    $(".emoji-menu-content").niceScroll({cursorwidth: "7px", autohidemode: false})
diff --git a/app/workers/build_email_worker.rb b/app/workers/build_email_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1c7a04a66a819e59c350cba67eb35ac6b19549af
--- /dev/null
+++ b/app/workers/build_email_worker.rb
@@ -0,0 +1,19 @@
+class BuildEmailWorker
+  include Sidekiq::Worker
+
+  def perform(build_id, recipients, push_data)
+    recipients.each do |recipient|
+      begin
+        case push_data['build_status']
+        when 'success'
+          Notify.build_success_email(build_id, recipient).deliver_now
+        when 'failed'
+          Notify.build_fail_email(build_id, recipient).deliver_now
+        end
+      # These are input errors and won't be corrected even if Sidekiq retries
+      rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
+        logger.info("Failed to send e-mail for project '#{push_data['project_name']}' to #{recipient}: #{e}")
+      end
+    end
+  end
+end
diff --git a/app/workers/ci/hip_chat_notifier_worker.rb b/app/workers/ci/hip_chat_notifier_worker.rb
deleted file mode 100644
index ebb43570e2a4375f3d2ca408dc3cefa178bca36d..0000000000000000000000000000000000000000
--- a/app/workers/ci/hip_chat_notifier_worker.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module Ci
-  class HipChatNotifierWorker
-    include Sidekiq::Worker
-
-    def perform(message, options={})
-      room   = options.delete('room')
-      token  = options.delete('token')
-      server = options.delete('server')
-      name   = options.delete('service_name')
-      client_opts = {
-        api_version: 'v2',
-        server_url: server
-      }
-
-      client = HipChat::Client.new(token, client_opts)
-      client[room].send(name, message, options.symbolize_keys)
-    end
-  end
-end
diff --git a/app/workers/ci/slack_notifier_worker.rb b/app/workers/ci/slack_notifier_worker.rb
deleted file mode 100644
index 3bbb9b4bec7a54454199c14c31c1c35a8cd41a38..0000000000000000000000000000000000000000
--- a/app/workers/ci/slack_notifier_worker.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module Ci
-  class SlackNotifierWorker
-    include Sidekiq::Worker
-
-    def perform(webhook_url, message, options={})
-      notifier = Slack::Notifier.new(webhook_url)
-      notifier.ping(message, options)
-    end
-  end
-end
diff --git a/app/workers/ci/web_hook_worker.rb b/app/workers/ci/web_hook_worker.rb
deleted file mode 100644
index 0bb83845572e885ff329982787dcb0f203ff3ef8..0000000000000000000000000000000000000000
--- a/app/workers/ci/web_hook_worker.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module Ci
-  class WebHookWorker
-    include Sidekiq::Worker
-
-    def perform(hook_id, data)
-      Ci::WebHook.find(hook_id).execute data
-    end
-  end
-end
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index 5a921a73fe9b46faea572994d9d7c65d9d60f6be..f2649e38eb34baafeaf2eded7f17821ff46ade8f 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -46,6 +46,6 @@ class EmailReceiverWorker
       return
     end
 
-    EmailRejectionMailer.delay.rejection(reason, raw, can_retry)
+    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 916a99bb273b8aadb85a6ff45541d4209d2dabc3..c4d8595d45dc9f9297c07ae75a0438aafcf59691 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -53,7 +53,7 @@ class EmailsOnPushWorker
           reverse_compare:            reverse_compare,
           send_from_committer_email:  send_from_committer_email,
           disable_diffs:              disable_diffs
-        ).deliver
+        ).deliver_now
       # These are input errors and won't be corrected even if Sidekiq retries
       rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
         logger.info("Failed to send e-mail for project '#{project.name_with_namespace}' to #{recipient}: #{e}")
diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb
index 5d1a8555b7d69e08b7071f0c163de4e7189bdd8d..c87c0a252b1f56f6489c4cf505ccd5e91f86e2c2 100644
--- a/app/workers/merge_worker.rb
+++ b/app/workers/merge_worker.rb
@@ -8,16 +8,7 @@ class MergeWorker
     current_user = User.find(current_user_id)
     merge_request = MergeRequest.find(merge_request_id)
 
-    result = MergeRequests::MergeService.new(merge_request.target_project, current_user).
-      execute(merge_request, params[:commit_message])
-
-    if result[:status] == :success && params[:should_remove_source_branch].present?
-      DeleteBranchService.new(merge_request.source_project, current_user).
-        execute(merge_request.source_branch)
-
-      merge_request.source_project.repository.expire_branch_names
-    end
-
-    result
+    MergeRequests::MergeService.new(merge_request.target_project, current_user, params).
+      execute(merge_request)
   end
 end
diff --git a/app/workers/stuck_ci_builds_worker.rb b/app/workers/stuck_ci_builds_worker.rb
index ad02a3b16d953b46eb5901e2be171a2211aa4382..ca594e77e7cd844693b42d7b7c21c15fc6ed1741 100644
--- a/app/workers/stuck_ci_builds_worker.rb
+++ b/app/workers/stuck_ci_builds_worker.rb
@@ -1,11 +1,8 @@
 class StuckCiBuildsWorker
   include Sidekiq::Worker
-  include Sidetiq::Schedulable
 
   BUILD_STUCK_TIMEOUT = 1.day
 
-  recurrence { daily }
-
   def perform
     Rails.logger.info 'Cleaning stuck builds'
 
@@ -14,5 +11,8 @@ class StuckCiBuildsWorker
       Rails.logger.debug "Dropping stuck #{build.status} build #{build.id} for runner #{build.runner_id}"
       build.drop
     end
+
+    # Update builds that failed to drop
+    builds.update_all(status: 'failed')
   end
 end
diff --git a/bin/background_jobs b/bin/background_jobs
index d4578f6a22278049b8b32b5e5d36493709974682..5c85fb339e65c76eeeaa8b53c2b87565d63aa44e 100755
--- a/bin/background_jobs
+++ b/bin/background_jobs
@@ -37,7 +37,7 @@ start_no_deamonize()
 
 start_sidekiq()
 {
-  bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
+  bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
 }
 
 load_ok()
diff --git a/bin/ci/upgrade.rb b/bin/ci/upgrade.rb
old mode 100644
new mode 100755
diff --git a/bin/parallel-rsync-repos b/bin/parallel-rsync-repos
new file mode 100755
index 0000000000000000000000000000000000000000..21921148fa0913558473077c23f1b17bea8701fd
--- /dev/null
+++ b/bin/parallel-rsync-repos
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+# this script should run as the 'git' user, not root, because 'root' should not
+# own intermediate directories created by rsync.
+#
+# Example invocation:
+# find /var/opt/gitlab/git-data/repositories -maxdepth 2 | \
+#   parallel-rsync-repos transfer-success.log /var/opt/gitlab/git-data/repositories /mnt/gitlab/repositories
+#
+# You can also rsync to a remote destination.
+#
+# parallel-rsync-repos transfer-success.log /var/opt/gitlab/git-data/repositories user@host:/mnt/gitlab/repositories
+#
+# If you need to pass extra options to rsync, set the RSYNC variable
+#
+# env RSYNC='rsync --rsh="foo bar"' parallel-rsync-repos transfer-success.log /src dest
+#
+
+LOGFILE=$1
+SRC=$2
+DEST=$3
+
+if [ -z "$LOGFILE" ] || [ -z "$SRC" ] || [ -z "$DEST" ] ; then
+  echo "Usage: $0 LOGFILE SRC DEST"
+  exit 1
+fi
+
+if [ -z "$JOBS" ] ; then
+  JOBS=10
+fi
+
+if [ -z "$RSYNC" ] ; then
+  RSYNC=rsync
+fi
+
+if ! cd $SRC ; then
+  echo "cd $SRC failed"
+  exit 1
+fi
+
+rsyncjob() {
+  relative_dir="./${1#$SRC}"
+
+  if ! $RSYNC --delete --relative -a "$relative_dir" "$DEST" ; then
+    echo "rsync $1 failed"
+    return 1
+  fi
+
+  echo "$1" >> $LOGFILE
+}
+
+export LOGFILE SRC DEST RSYNC
+export -f rsyncjob
+
+parallel -j$JOBS --progress rsyncjob
diff --git a/bin/rails b/bin/rails
index 7feb6a30e696bf9b94f204c6abb5c4d75f965046..5191e6927af7238928ada521f47e6c3457371baf 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,8 +1,4 @@
 #!/usr/bin/env ruby
-begin
-  load File.expand_path("../spring", __FILE__)
-rescue LoadError
-end
-APP_PATH = File.expand_path('../../config/application',  __FILE__)
+APP_PATH = File.expand_path('../../config/application', __FILE__)
 require_relative '../config/boot'
 require 'rails/commands'
diff --git a/bin/rake b/bin/rake
index 0fb4e07e13ab9fdf8a7a2a86d0f7426432230bcf..17240489f64832c9ce080088e27780d3dc3ee29a 100755
--- a/bin/rake
+++ b/bin/rake
@@ -1,7 +1,4 @@
 #!/usr/bin/env ruby
-begin
-  load File.expand_path("../spring", __FILE__)
-rescue LoadError
-end
-require 'bundler/setup'
-load Gem.bin_path('rake', 'rake')
+require_relative '../config/boot'
+require 'rake'
+Rake.application.run
diff --git a/bin/setup b/bin/setup
new file mode 100755
index 0000000000000000000000000000000000000000..acdb2c1389c502f79a967384cac1c5adcea10ec2
--- /dev/null
+++ b/bin/setup
@@ -0,0 +1,29 @@
+#!/usr/bin/env ruby
+require 'pathname'
+
+# path to your application root.
+APP_ROOT = Pathname.new File.expand_path('../../',  __FILE__)
+
+Dir.chdir APP_ROOT do
+  # This script is a starting point to setup your application.
+  # Add necessary setup steps to this file:
+
+  puts "== Installing dependencies =="
+  system "gem install bundler --conservative"
+  system "bundle check || bundle install"
+
+  # puts "\n== Copying sample files =="
+  # unless File.exist?("config/database.yml")
+  #   system "cp config/database.yml.sample config/database.yml"
+  # end
+
+  puts "\n== Preparing database =="
+  system "bin/rake db:setup"
+
+  puts "\n== Removing old logs and tempfiles =="
+  system "rm -f log/*"
+  system "rm -rf tmp/cache"
+
+  puts "\n== Restarting application server =="
+  system "touch tmp/restart.txt"
+end
diff --git a/bin/upgrade.rb b/bin/upgrade.rb
old mode 100644
new mode 100755
diff --git a/config/application.rb b/config/application.rb
index bfa2a809dd7d7fd472990b00d6f38c8948fd36ce..d255ff0719f52405d2d0d9eac1868d6bb4027d03 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -99,6 +99,10 @@ module Gitlab
     redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
     config.cache_store = :redis_store, redis_config_hash
 
+    config.active_record.raise_in_transactional_callbacks = true
+
+    config.active_job.queue_adapter = :sidekiq
+
     # This is needed for gitlab-shell
     ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH']
   end
diff --git a/config/database.yml.env b/config/database.yml.env
new file mode 100644
index 0000000000000000000000000000000000000000..b2ff23cb5abda9327b0e39d8bb5564b14cd092fe
--- /dev/null
+++ b/config/database.yml.env
@@ -0,0 +1,9 @@
+<%= ENV['RAILS_ENV'] %>:
+  adapter: <%= ENV['GITLAB_DATABASE_ADAPTER'] || 'postgresql' %>
+  encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %>
+  database: <%= ENV['GITLAB_DATABASE_DATABASE'] || "gitlab_#{ENV['RAILS_ENV']}" %>
+  pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %>
+  username: <%= ENV['GITLAB_DATABASE_USERNAME'] || 'root' %>
+  password: <%= ENV['GITLAB_DATABASE_PASSWORD'] || '' %>
+  host: <%= ENV['GITLAB_DATABASE_HOST'] || 'localhost' %>
+  port: <%= ENV['GITLAB_DATABASE_PORT'] || '5432' %>
diff --git a/config/environment.rb b/config/environment.rb
index 3b186a9d57afddfdae94306877719d2b98ea1945..df3006d349c2ce2456c6225cb0fcdf1f621c4390 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -2,4 +2,4 @@
 require File.expand_path('../application', __FILE__)
 
 # Initialize the rails application
-Gitlab::Application.initialize!
+Rails.application.initialize!
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 827a110c2492ac5349277065d2a992243760bc75..c22722c606bd8cd6f30c869a425223a71823dc30 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -1,4 +1,4 @@
-Gitlab::Application.configure do
+Rails.application.configure do
   # Settings specified here will take precedence over those in config/application.rb
 
   # In the development environment your application's code is reloaded on
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 3316ece387398bf8188c9a5fb0aee12315483f50..909526605a194da423811eba359549c1e1de5391 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -1,4 +1,4 @@
-Gitlab::Application.configure do
+Rails.application.configure do
   # Settings specified here will take precedence over those in config/application.rb
 
   # Code is not reloaded between requests
@@ -9,7 +9,7 @@ Gitlab::Application.configure do
   config.action_controller.perform_caching = true
 
   # Disable Rails's static asset server (Apache or nginx will already do this)
-  config.serve_static_assets = false
+  config.serve_static_files = false
 
   # Compress JavaScripts and CSS.
   config.assets.js_compressor = :uglifier
@@ -32,7 +32,7 @@ Gitlab::Application.configure do
   # config.force_ssl = true
 
   # See everything in the log (default is :info)
-  # config.log_level = :debug
+  config.log_level = :info
 
   # Suppress 'Rendered template ...' messages in the log
   # source: http://stackoverflow.com/a/16369363
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 955540837d31dccf6bf122a217b54ca086e1eaed..d6842affa6c19ba06694766f7720e6590e50adcc 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -1,4 +1,4 @@
-Gitlab::Application.configure do
+Rails.application.configure do
   # Settings specified here will take precedence over those in config/application.rb
 
   # The test environment is used exclusively to run your application's
@@ -7,8 +7,10 @@ Gitlab::Application.configure do
   # and recreated between test runs. Don't rely on the data there!
   config.cache_classes = false
 
+  config.cache_store = :null_store
+
   # Configure static asset server for tests with Cache-Control for performance
-  config.serve_static_assets = true
+  config.serve_static_files = true
   config.static_cache_control = "public, max-age=3600"
 
   # Show full error reports and disable caching
@@ -32,4 +34,6 @@ Gitlab::Application.configure do
   config.eager_load = false
 
   config.cache_store = :null_store
+
+  config.active_job.queue_adapter = :test
 end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 1788d4c2c7cab4e0b2133f47c4b84e1ec55fca22..2d9f730c1831e6de76b8d297089b484449588553 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -4,8 +4,8 @@
 #
 ###########################  NOTE  #####################################
 # This file should not receive new settings. All configuration options #
-# that do not require an application restart are being moved to        #
-# ApplicationSetting model!                                            #
+# * are being moved to ApplicationSetting model!                       #
+# If a setting requires an application restart say so in that screen.  #
 # If you change this file in a Merge Request, please also create       #
 # a MR on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests  #
 ########################################################################
@@ -76,7 +76,7 @@ production: &base
     # This happens when the commit is pushed or merged into the default branch of a project.
     # When not specified the default issue_closing_pattern as specified below will be used.
     # Tip: you can test your closing pattern at http://rubular.com.
-    # issue_closing_pattern: '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)'
+    # issue_closing_pattern: '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)'
 
     ## Default project features settings
     default_projects_features:
@@ -124,6 +124,12 @@ production: &base
     # The mailbox where incoming mail will end up. Usually "inbox".
     mailbox: "inbox"
 
+  ## Build Artifacts
+  artifacts:
+    enabled: true
+    # The location where build artifacts are stored (default: shared/artifacts).
+    # path: shared/artifacts
+
   ## Git LFS
   lfs:
     enabled: true
@@ -138,6 +144,15 @@ production: &base
     # plain_url: "http://..."     # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
     # ssl_url:   "https://..."    # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
 
+  ## Auxiliary jobs
+  # Periodically executed jobs, to self-heal Gitlab, do external synchronizations, etc.
+  # Please read here for more information: https://github.com/ondrejbartas/sidekiq-cron#adding-cron-job
+  cron_jobs:
+    # Flag stuck CI builds as failed
+    stuck_ci_builds_worker:
+      cron: "0 0 * * *"
+
+
   #
   # 2. GitLab CI settings
   # ==========================
@@ -281,6 +296,15 @@ production: &base
     # arguments, followed by optional 'args' which can be either a hash or an array.
     # Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html
     providers:
+      # See omniauth-cas3 for more configuration details
+      # - { name: 'cas3',
+      #     label: 'cas3',
+      #     args: {
+      #             url: 'https://sso.example.com',
+      #             disable_ssl_verification: false,
+      #             login_url: '/cas/login',
+      #             service_validate_url: '/cas/p3/serviceValidate',
+      #             logout_url: '/cas/logout'} }
       # - { name: 'github',
       #     app_id: 'YOUR_APP_ID',
       #     app_secret: 'YOUR_APP_SECRET',
@@ -318,6 +342,10 @@ production: &base
       #       application_name: 'YOUR_APP_NAME',
       #       application_password: 'YOUR_APP_PASSWORD' } }
 
+    # SSO maximum session duration in seconds. Defaults to CAS default of 8 hours.
+    # cas3:
+    #   session_duration: 28800
+
   # Shared file storage settings
   shared:
     # path: /mnt/gitlab # Default: shared
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index b498fb1e1da7d2c8976de63aca9dd5f3e573bf7e..dea59f4fec850d5f74ae44c975ebdd373171b73f 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -33,13 +33,15 @@ class Settings < Settingslogic
     end
 
     def build_gitlab_shell_ssh_path_prefix
+      user_host = "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}"
+
       if gitlab_shell.ssh_port != 22
-        "ssh://#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:#{gitlab_shell.ssh_port}/"
+        "ssh://#{user_host}:#{gitlab_shell.ssh_port}/"
       else
         if gitlab_shell.ssh_host.include? ':'
-          "[#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}]:"
+          "[#{user_host}]:"
         else
-          "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:"
+          "#{user_host}:"
         end
       end
     end
@@ -124,6 +126,11 @@ Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block
 Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil?
 
 Settings.omniauth['providers']  ||= []
+Settings.omniauth['cas3'] ||= Settingslogic.new({})
+Settings.omniauth.cas3['session_duration'] ||= 8.hours
+Settings.omniauth['session_tickets'] ||= Settingslogic.new({})
+Settings.omniauth.session_tickets['cas3'] = 'ticket'
+
 
 Settings['shared'] ||= Settingslogic.new({})
 Settings.shared['path'] = File.expand_path(Settings.shared['path'] || "shared", Rails.root)
@@ -138,7 +145,7 @@ Settings.gitlab['default_projects_limit'] ||= 10
 Settings.gitlab['default_branch_protection'] ||= 2
 Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
 Settings.gitlab['default_theme'] = Gitlab::Themes::APPLICATION_DEFAULT if Settings.gitlab['default_theme'].nil?
-Settings.gitlab['host']       ||= 'localhost'
+Settings.gitlab['host']       ||= ENV['GITLAB_HOST'] || 'localhost'
 Settings.gitlab['ssh_host']   ||= Settings.gitlab.host
 Settings.gitlab['https']        = false if Settings.gitlab['https'].nil?
 Settings.gitlab['port']       ||= Settings.gitlab.https ? 443 : 80
@@ -162,7 +169,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].
 Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
 Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
 Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
-Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
+Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z]*-\d*))+)' if Settings.gitlab['issue_closing_pattern'].nil?
 Settings.gitlab['default_projects_features'] ||= {}
 Settings.gitlab['webhook_timeout'] ||= 10
 Settings.gitlab['max_attachment_size'] ||= 10
@@ -187,7 +194,6 @@ Settings.gitlab_ci['all_broken_builds']     = true if Settings.gitlab_ci['all_br
 Settings.gitlab_ci['add_pusher']            = false if Settings.gitlab_ci['add_pusher'].nil?
 Settings.gitlab_ci['url']                   ||= Settings.send(:build_gitlab_ci_url)
 Settings.gitlab_ci['builds_path']           = File.expand_path(Settings.gitlab_ci['builds_path'] || "builds/", Rails.root)
-Settings.gitlab_ci['max_artifacts_size']    ||= 100 # in megabytes
 
 #
 # Reply by email
@@ -199,6 +205,14 @@ Settings.incoming_email['ssl']        = false if Settings.incoming_email['ssl'].
 Settings.incoming_email['start_tls']  = false if Settings.incoming_email['start_tls'].nil?
 Settings.incoming_email['mailbox']    = "inbox" if Settings.incoming_email['mailbox'].nil?
 
+#
+# Build Artifacts
+#
+Settings['artifacts'] ||= Settingslogic.new({})
+Settings.artifacts['enabled']      = true if Settings.artifacts['enabled'].nil?
+Settings.artifacts['path']         = File.expand_path(Settings.artifacts['path'] || File.join(Settings.shared['path'], "artifacts"), Rails.root)
+Settings.artifacts['max_size']    ||= 100 # in megabytes
+
 #
 # Git LFS
 #
@@ -215,6 +229,15 @@ Settings.gravatar['plain_url']  ||= 'http://www.gravatar.com/avatar/%{hash}?s=%{
 Settings.gravatar['ssl_url']    ||= 'https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon'
 Settings.gravatar['host']         = Settings.get_host_without_www(Settings.gravatar['plain_url'])
 
+#
+# Cron Jobs
+#
+Settings['cron_jobs'] ||= Settingslogic.new({})
+Settings.cron_jobs['stuck_ci_builds_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['stuck_ci_builds_worker']['cron'] ||= '0 0 * * *'
+Settings.cron_jobs['stuck_ci_builds_worker']['job_class'] = 'StuckCiBuildsWorker'
+
+
 #
 # GitLab Shell
 #
@@ -286,3 +309,12 @@ if Rails.env.test?
   Settings.gitlab['default_can_create_group'] = true
   Settings.gitlab['default_can_create_team']  = false
 end
+
+# Force a refresh of application settings at startup
+begin
+  ApplicationSetting.expire
+  Ci::ApplicationSetting.expire
+rescue
+  # Gracefully handle when Redis is not available. For example,
+  # omnibus may fail here during assets:precompile.
+end
diff --git a/config/initializers/4_ci_app.rb b/config/initializers/4_ci_app.rb
index cac8edb32bf3963ca97f09dff9bb3e37516ee4ae..d252e403102ca950ade8d76c3ae5289fc0a1b2e0 100644
--- a/config/initializers/4_ci_app.rb
+++ b/config/initializers/4_ci_app.rb
@@ -1,8 +1,6 @@
 module GitlabCi
   VERSION = Gitlab::VERSION
   REVISION = Gitlab::REVISION
-  
-  REGISTRATION_TOKEN = SecureRandom.hex(10)
 
   def self.config
     Settings
diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb
index bfb8656df552da088bf7848deaa77540945bead2..df28d30d750946bf32d3d4e5ae078bc7e3919ab0 100644
--- a/config/initializers/carrierwave.rb
+++ b/config/initializers/carrierwave.rb
@@ -31,11 +31,11 @@ if File.exists?(aws_file)
   if Rails.env.test?
     Fog.mock!
     connection = ::Fog::Storage.new(
-        aws_access_key_id: AWS_CONFIG['access_key_id'],
-        aws_secret_access_key: AWS_CONFIG['secret_access_key'],
-        provider: 'AWS',
-        region: AWS_CONFIG['region']
-      )
+      aws_access_key_id: AWS_CONFIG['access_key_id'],
+      aws_secret_access_key: AWS_CONFIG['secret_access_key'],
+      provider: 'AWS',
+      region: AWS_CONFIG['region']
+    )
     connection.directories.create(key: AWS_CONFIG['bucket'])
   end
 end
diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb
index 43adac8b2c6c2a80fba8bb41bbc3813407338d1f..54516e3f23da72aea2680c1122d3c22353e104c0 100644
--- a/config/initializers/cookies_serializer.rb
+++ b/config/initializers/cookies_serializer.rb
@@ -1,3 +1,3 @@
 # Be sure to restart your server when you modify this file.
 
-Gitlab::Application.config.action_dispatch.cookies_serializer = :hybrid
+Rails.application.config.action_dispatch.cookies_serializer = :hybrid
diff --git a/config/initializers/default_url_options.rb b/config/initializers/default_url_options.rb
index f9f88f95db98ce2c4d8a902dcada54a855968510..8fd27b1d88e4f80349314c6f016407822e2e5b53 100644
--- a/config/initializers/default_url_options.rb
+++ b/config/initializers/default_url_options.rb
@@ -8,4 +8,4 @@ unless Gitlab.config.gitlab_on_standard_port?
   default_url_options[:port] = Gitlab.config.gitlab.port
 end
 
-Gitlab::Application.routes.default_url_options = default_url_options
+Rails.application.routes.default_url_options = default_url_options
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 29506970af24bfb5c4494d705e847fa7580e746a..d82cfb3ec0c787022f6b61fcab2f5104f0fee8f0 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -60,7 +60,7 @@ Devise.setup do |config|
   # It will change confirmation, password recovery and other workflows
   # to behave the same regardless if the e-mail provided was right or wrong.
   # Does not affect registerable.
-  # config.paranoid = true
+  config.paranoid = true
 
   # ==> Configuration for :database_authenticatable
   # For bcrypt, this is the cost for hashing the password and defaults to 10. If
@@ -121,14 +121,14 @@ Devise.setup do |config|
   config.lock_strategy = :failed_attempts
 
   # Defines which key will be used when locking and unlocking an account
-  # config.unlock_keys = [ :email ]
+  config.unlock_keys = [ :email ]
 
   # Defines which strategy will be used to unlock an account.
   # :email = Sends an unlock link to the user email
   # :time  = Re-enables login after a certain amount of time (see :unlock_in below)
   # :both  = Enables both strategies
   # :none  = No unlock strategy. You should handle unlocking by yourself.
-  config.unlock_strategy = :time
+  config.unlock_strategy = :both
 
   # Number of authentication tries before locking an account if lock_strategy
   # is failed attempts.
@@ -241,6 +241,16 @@ Devise.setup do |config|
       # An Array from the configuration will be expanded.
       provider_arguments.concat provider['args']
     when Hash
+      # Add procs for handling SLO
+      if provider['name'] == 'cas3'
+        provider['args'][:on_single_sign_out]  = lambda do |request|
+          ticket = request.params[:session_index]
+          raise "Service Ticket not found." unless Gitlab::OAuth::Session.valid?(:cas3, ticket)
+          Gitlab::OAuth::Session.destroy(:cas3, ticket)
+          true
+        end
+      end
+
       # A Hash from the configuration will be passed as is.
       provider_arguments << provider['args'].symbolize_keys
     end
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2e4908192a13ba6d03a3f3fec455fd2a9661cec7
--- /dev/null
+++ b/config/initializers/metrics.rb
@@ -0,0 +1,64 @@
+if Gitlab::Metrics.enabled?
+  require 'influxdb'
+  require 'socket'
+  require 'connection_pool'
+  require 'method_source'
+
+  # These are manually require'd so the classes are registered properly with
+  # ActiveSupport.
+  require 'gitlab/metrics/subscribers/action_view'
+  require 'gitlab/metrics/subscribers/active_record'
+
+  Gitlab::Application.configure do |config|
+    config.middleware.use(Gitlab::Metrics::RackMiddleware)
+  end
+
+  Sidekiq.configure_server do |config|
+    config.server_middleware do |chain|
+      chain.add Gitlab::Metrics::SidekiqMiddleware
+    end
+  end
+
+  # This instruments all methods residing in app/models that (appear to) use any
+  # of the ActiveRecord methods. This has to take place _after_ initializing as
+  # for some unknown reason calling eager_load! earlier breaks Devise.
+  Gitlab::Application.config.after_initialize do
+    Rails.application.eager_load!
+
+    models = Rails.root.join('app', 'models').to_s
+
+    regex = Regexp.union(
+      ActiveRecord::Querying.public_instance_methods(false).map(&:to_s)
+    )
+
+    Gitlab::Metrics::Instrumentation.
+      instrument_class_hierarchy(ActiveRecord::Base) do |klass, method|
+        # Instrumenting the ApplicationSetting class can lead to an infinite
+        # loop. Since the data is cached any way we don't really need to
+        # instrument it.
+        if klass == ApplicationSetting
+          false
+        else
+          loc = method.source_location
+
+          loc && loc[0].start_with?(models) && method.source =~ regex
+        end
+      end
+  end
+
+  Gitlab::Metrics::Instrumentation.configure do |config|
+    config.instrument_instance_methods(Gitlab::Shell)
+
+    config.instrument_methods(Gitlab::Git)
+
+    Gitlab::Git.constants.each do |name|
+      const = Gitlab::Git.const_get(name)
+
+      config.instrument_methods(const) if const.is_a?(Module)
+    end
+  end
+
+  GC::Profiler.enable
+
+  Gitlab::Metrics::Sampler.new.start
+end
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb
index 70ed10e8275ef461c703d98064f2b31cf3fb643f..4c164119fff1db68d07d60b6dfbfafcc8d63a731 100644
--- a/config/initializers/omniauth.rb
+++ b/config/initializers/omniauth.rb
@@ -16,7 +16,7 @@ OmniAuth.config.allowed_request_methods = [:post]
 #In case of auto sign-in, the GET method is used (users don't get to click on a button)
 OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
 OmniAuth.config.before_request_phase do |env|
-  OmniAuth::RequestForgeryProtection.new(env).call
+  OmniAuth::RequestForgeryProtection.call(env)
 end
 
 if Gitlab.config.omniauth.enabled
diff --git a/config/initializers/rack_attack.rb.example b/config/initializers/rack_attack.rb.example
index 2155ea145625eee86519d48cfc0a933669a5a7ed..b1bbcca1d615f8ab53c3332cdeecc365a4eb2e11 100644
--- a/config/initializers/rack_attack.rb.example
+++ b/config/initializers/rack_attack.rb.example
@@ -4,13 +4,13 @@
 # If you change this file in a Merge Request, please also create a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
 
 paths_to_be_protected = [
-  "#{Gitlab::Application.config.relative_url_root}/users/password",
-  "#{Gitlab::Application.config.relative_url_root}/users/sign_in",
-  "#{Gitlab::Application.config.relative_url_root}/api/#{API::API.version}/session.json",
-  "#{Gitlab::Application.config.relative_url_root}/api/#{API::API.version}/session",
-  "#{Gitlab::Application.config.relative_url_root}/users",
-  "#{Gitlab::Application.config.relative_url_root}/users/confirmation",
-  "#{Gitlab::Application.config.relative_url_root}/unsubscribes/"
+  "#{Rails.application.config.relative_url_root}/users/password",
+  "#{Rails.application.config.relative_url_root}/users/sign_in",
+  "#{Rails.application.config.relative_url_root}/api/#{API::API.version}/session.json",
+  "#{Rails.application.config.relative_url_root}/api/#{API::API.version}/session",
+  "#{Rails.application.config.relative_url_root}/users",
+  "#{Rails.application.config.relative_url_root}/users/confirmation",
+  "#{Rails.application.config.relative_url_root}/unsubscribes/"
 
 ]
 
diff --git a/config/initializers/rack_lineprof.rb b/config/initializers/rack_lineprof.rb
index f0c006d811bffe04c02572441f7d5cc329ba0255..22e77a32c614bffa2d0709d46298cf21e3da6d49 100644
--- a/config/initializers/rack_lineprof.rb
+++ b/config/initializers/rack_lineprof.rb
@@ -2,7 +2,7 @@
 # with darker backgrounds. This patch tweaks the colors a bit so the output is
 # actually readable.
 if Rails.env.development? and RUBY_ENGINE == 'ruby' and ENV['ENABLE_LINEPROF']
-  Gitlab::Application.config.middleware.use(Rack::Lineprof)
+  Rails.application.config.middleware.use(Rack::Lineprof)
 
   module Rack
     class Lineprof
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
index 1b518c3becf51104c9c52f88413ae78ccf0c4d01..dae3a4a9a93f26da1b6c9289bf806639508b38c3 100644
--- a/config/initializers/secret_token.rb
+++ b/config/initializers/secret_token.rb
@@ -22,15 +22,15 @@ def find_secure_token
   end
 end
 
-Gitlab::Application.config.secret_token = find_secure_token
-Gitlab::Application.config.secret_key_base = find_secure_token
+Rails.application.config.secret_token = find_secure_token
+Rails.application.config.secret_key_base = find_secure_token
 
 # CI
 def generate_new_secure_token
   SecureRandom.hex(64)
 end
 
-if Gitlab::Application.secrets.db_key_base.blank?
+if Rails.application.secrets.db_key_base.blank?
   warn "Missing `db_key_base` for '#{Rails.env}' environment. The secrets will be generated and stored in `config/secrets.yml`"
 
   all_secrets = YAML.load_file('config/secrets.yml') if File.exist?('config/secrets.yml')
@@ -46,5 +46,5 @@ if Gitlab::Application.secrets.db_key_base.blank?
     file.write(YAML.dump(all_secrets))
   end
 
-  Gitlab::Application.secrets.db_key_base = env_secrets['db_key_base']
+  Rails.application.secrets.db_key_base = env_secrets['db_key_base']
 end
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index d7c5432da76553e310ab3ae7dcdc9ac827a8c425..0fc725842ba16fd738338faa4443b257362061ee 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -3,20 +3,23 @@
 require 'gitlab/current_settings'
 include Gitlab::CurrentSettings
 
-# allow it to fail: it may to do so when create_from_defaults is executed before migrations are actually done
+# allow it to fail: it may do so when create_from_defaults is executed before migrations are actually done
 begin
-  Settings.gitlab['session_expire_delay'] = current_application_settings.session_expire_delay
+  Settings.gitlab['session_expire_delay'] = current_application_settings.session_expire_delay || 10080
 rescue
+  Settings.gitlab['session_expire_delay'] ||= 10080
 end
 
-unless Rails.env.test?
+if Rails.env.test?
+  Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session"
+else
   Gitlab::Application.config.session_store(
     :redis_store, # Using the cookie_store would enable session replay attacks.
-    servers: Gitlab::Application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store
+    servers: Rails.application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store
     key: '_gitlab_session',
     secure: Gitlab.config.gitlab.https,
     httponly: true,
     expire_after: Settings.gitlab['session_expire_delay'] * 60,
-    path: (Gitlab::Application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root
+    path: (Rails.application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root
   )
 end
diff --git a/config/initializers/sherlock.rb b/config/initializers/sherlock.rb
index 42b0d78c85fae51d6c800ab2cc66b0e77afb75a7..8f2ababb712f93189f18fd4a9704f06e58d55492 100644
--- a/config/initializers/sherlock.rb
+++ b/config/initializers/sherlock.rb
@@ -1,5 +1,5 @@
 if Gitlab::Sherlock.enabled?
-  Gitlab::Application.configure do |config|
+  Rails.application.configure do |config|
     config.middleware.use(Gitlab::Sherlock::Middleware)
   end
 end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index e856499732e949334e401a6740846f1d706ba24b..dcf6ce74d96bbd1dc546a6d0934e5a611d7b8cd7 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -17,6 +17,21 @@ Sidekiq.configure_server do |config|
     chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
     chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
   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') }
+  Sidekiq::Cron::Job.load_from_hash! cron_jobs
+
+  # Database pool should be at least `sidekiq_concurrency` + 2
+  # For more info, see: https://github.com/mperham/sidekiq/blob/master/4.0-Upgrade.md
+  config = ActiveRecord::Base.configurations[Rails.env] ||
+                Rails.application.config.database_configuration[Rails.env]
+  config['pool'] = Sidekiq.options[:concurrency] + 2
+  ActiveRecord::Base.establish_connection(config)
+  Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
 end
 
 Sidekiq.configure_client do |config|
diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample
index 25ec247a0954b709c18327a1fe8f16411a96d05d..ec182502d4e38afdf0453eafefce3c9ccf0f92e5 100644
--- a/config/initializers/smtp_settings.rb.sample
+++ b/config/initializers/smtp_settings.rb.sample
@@ -8,7 +8,7 @@
 # If you change this file in a Merge Request, please also create a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
 
 if Rails.env.production?
-  Gitlab::Application.config.action_mailer.delivery_method = :smtp
+  Rails.application.config.action_mailer.delivery_method = :smtp
 
   ActionMailer::Base.smtp_settings = {
     address: "email.server.com",
diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb
index e6d5600edb7e4f5b4f1c75cbb6915a29c311d67a..d6dbf8b9fbfca8d9edc43a8e09a650cd1a3c5904 100644
--- a/config/initializers/static_files.rb
+++ b/config/initializers/static_files.rb
@@ -1,6 +1,6 @@
-app = Gitlab::Application
+app = Rails.application
 
-if app.config.serve_static_assets
+if app.config.serve_static_files
   # The `ActionDispatch::Static` middleware intercepts requests for static files 
   # by checking if they exist in the `/public` directory. 
   # We're replacing it with our `Gitlab::Middleware::Static` that does the same,
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index 22070e37f07f3b1d76df813784e2474fffbf06c8..bd4c3ebc69ec163f497e6c113bba50c94d7b7a24 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -30,7 +30,6 @@ en:
       success: "Successfully authenticated from %{kind} account."
     passwords:
       no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
-      recently_reset: "Instructions about how to reset your password have already been sent recently. Please wait a few minutes to try again."
       send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
       send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
       updated: "Your password has been changed successfully. You are now signed in."
diff --git a/config/locales/sherlock.en.yml b/config/locales/sherlock.en.yml
index 683b09dc3294d5710850802193572470bee0f5d9..f24b825f585410490fb44456aef97cbc0e1033d1 100644
--- a/config/locales/sherlock.en.yml
+++ b/config/locales/sherlock.en.yml
@@ -35,3 +35,4 @@ en:
     events: Events
     percent: '%'
     count: Count
+    query_time: Query Time
diff --git a/config/routes.rb b/config/routes.rb
index ac81a2aac7690c1a7043fec7403826d5bb22614b..3e7d9f78710fcc31b1ee2efbf5e25bc742278f8f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,7 +1,8 @@
 require 'sidekiq/web'
+require 'sidekiq/cron/web'
 require 'api/api'
 
-Gitlab::Application.routes.draw do
+Rails.application.routes.draw do
   if Gitlab::Sherlock.enabled?
     namespace :sherlock do
       resources :transactions, only: [:index, :show] do
@@ -23,43 +24,10 @@ Gitlab::Application.routes.draw do
     resource :lint, only: [:show, :create]
 
     resources :projects do
-      collection do
-        post :add
-        get :disabled
-      end
-
       member do
         get :status, to: 'projects#badge'
         get :integration
-        post :toggle_shared_runners
       end
-
-      resources :runner_projects, only: [:create, :destroy]
-    end
-
-    resource :user_sessions do
-      get :auth
-      get :callback
-    end
-
-    namespace :admin do
-      resources :runners, only: [:index, :show, :update, :destroy] do
-        member do
-          put :assign_all
-          get :resume
-          get :pause
-        end
-      end
-
-      resources :events, only: [:index]
-
-      resources :projects do
-        resources :runner_projects
-      end
-
-      resources :builds, only: :index
-
-      resource :application_settings, only: [:show, :update]
     end
 
     root to: 'projects#index'
@@ -220,7 +188,7 @@ Gitlab::Application.routes.draw do
   namespace :admin do
     resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
       resources :keys, only: [:show, :destroy]
-      resources :identities, only: [:index, :edit, :update, :destroy]
+      resources :identities, except: [:show]
 
       delete 'stop_impersonation' => 'impersonation#destroy', on: :collection
 
@@ -270,15 +238,31 @@ Gitlab::Application.routes.draw do
         member do
           put :transfer
         end
+
+        resources :runner_projects
       end
     end
 
     resource :application_settings, only: [:show, :update] do
       resources :services
+      put :reset_runners_token
     end
 
     resources :labels
 
+    resources :runners, only: [:index, :show, :update, :destroy] do
+      member do
+        get :resume
+        get :pause
+      end
+    end
+
+    resources :builds, only: :index do
+      collection do
+        post :cancel_all
+      end
+    end
+
     root to: 'dashboard#index'
   end
 
@@ -313,6 +297,7 @@ Gitlab::Application.routes.draw do
       resource :two_factor_auth, only: [:new, :create, :destroy] do
         member do
           post :codes
+          patch :skip
         end
       end
     end
@@ -368,7 +353,7 @@ Gitlab::Application.routes.draw do
       end
 
       resource :avatar, only: [:destroy]
-      resources :milestones, only: [:index, :show, :update, :new, :create]
+      resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
     end
   end
 
@@ -457,7 +442,7 @@ Gitlab::Application.routes.draw do
 
         scope do
           post(
-              '/create_dir/*id',
+            '/create_dir/*id',
               to: 'tree#create_dir',
               constraints: { id: /.+/ },
               as: 'create_dir'
@@ -499,6 +484,7 @@ Gitlab::Application.routes.draw do
           member do
             get :commits
             get :ci
+            get :languages
           end
         end
 
@@ -568,10 +554,12 @@ Gitlab::Application.routes.draw do
 
         resources :merge_requests, constraints: { id: /\d+/ }, except: [:destroy] do
           member do
-            get :diffs
             get :commits
-            post :merge
+            get :diffs
+            get :builds
             get :merge_check
+            post :merge
+            post :cancel_merge_when_build_succeeds
             get :ci_status
             post :toggle_subscription
           end
@@ -591,18 +579,6 @@ Gitlab::Application.routes.draw do
         resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
         resource :variables, only: [:show, :update]
         resources :triggers, only: [:index, :create, :destroy]
-        resource :ci_settings, only: [:edit, :update, :destroy]
-        resources :ci_web_hooks, only: [:index, :create, :destroy] do
-          member do
-            get :test
-          end
-        end
-
-        resources :ci_services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
-          member do
-            get :test
-          end
-        end
 
         resources :builds, only: [:index, :show] do
           collection do
@@ -681,7 +657,13 @@ Gitlab::Application.routes.draw do
             get :resume
             get :pause
           end
+
+          collection do
+            post :toggle_shared_runners
+          end
         end
+
+        resources :runner_projects, only: [:create, :destroy]
       end
     end
   end
diff --git a/db/migrate/20121220064453_init_schema.rb b/db/migrate/20121220064453_init_schema.rb
index 90f5eb08e8cdaf539a99f3c469e27977c6ae1908..d7644b6847af8aecfd5a307c5aa553bbbb0b419e 100644
--- a/db/migrate/20121220064453_init_schema.rb
+++ b/db/migrate/20121220064453_init_schema.rb
@@ -1,6 +1,6 @@
 class InitSchema < ActiveRecord::Migration
   def up
-    
+
     create_table "events", force: true do |t|
       t.string   "target_type"
       t.integer  "target_id"
@@ -12,14 +12,14 @@ class InitSchema < ActiveRecord::Migration
       t.integer  "action"
       t.integer  "author_id"
     end
-    
+
     add_index "events", ["action"], name: "index_events_on_action", using: :btree
     add_index "events", ["author_id"], name: "index_events_on_author_id", using: :btree
     add_index "events", ["created_at"], name: "index_events_on_created_at", using: :btree
     add_index "events", ["project_id"], name: "index_events_on_project_id", using: :btree
     add_index "events", ["target_id"], name: "index_events_on_target_id", using: :btree
     add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree
-    
+
     create_table "issues", force: true do |t|
       t.string   "title"
       t.integer  "assignee_id"
@@ -33,7 +33,7 @@ class InitSchema < ActiveRecord::Migration
       t.text     "description"
       t.integer  "milestone_id"
     end
-    
+
     add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
     add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
     add_index "issues", ["closed"], name: "index_issues_on_closed", using: :btree
@@ -41,7 +41,7 @@ class InitSchema < ActiveRecord::Migration
     add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
     add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree
     add_index "issues", ["title"], name: "index_issues_on_title", using: :btree
-    
+
     create_table "keys", force: true do |t|
       t.integer  "user_id"
       t.datetime "created_at"
@@ -51,11 +51,11 @@ class InitSchema < ActiveRecord::Migration
       t.string   "identifier"
       t.integer  "project_id"
     end
-    
+
     add_index "keys", ["identifier"], name: "index_keys_on_identifier", using: :btree
     add_index "keys", ["project_id"], name: "index_keys_on_project_id", using: :btree
     add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree
-    
+
     create_table "merge_requests", force: true do |t|
       t.string   "target_branch",                                    null: false
       t.string   "source_branch",                                    null: false
@@ -66,13 +66,13 @@ class InitSchema < ActiveRecord::Migration
       t.boolean  "closed",                           default: false, null: false
       t.datetime "created_at"
       t.datetime "updated_at"
-      t.text     "st_commits",    limit: 2147483647
-      t.text     "st_diffs",      limit: 2147483647
+      t.text     "st_commits"
+      t.text     "st_diffs"
       t.boolean  "merged",                           default: false, null: false
       t.integer  "state",                            default: 1,     null: false
       t.integer  "milestone_id"
     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", ["closed"], name: "index_merge_requests_on_closed", using: :btree
@@ -82,7 +82,7 @@ class InitSchema < ActiveRecord::Migration
     add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
     add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree
     add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree
-    
+
     create_table "milestones", force: true do |t|
       t.string   "title",                       null: false
       t.integer  "project_id",                  null: false
@@ -92,10 +92,10 @@ class InitSchema < ActiveRecord::Migration
       t.datetime "created_at"
       t.datetime "updated_at"
     end
-    
+
     add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree
     add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree
-    
+
     create_table "namespaces", force: true do |t|
       t.string   "name",       null: false
       t.string   "path",       null: false
@@ -104,12 +104,12 @@ class InitSchema < ActiveRecord::Migration
       t.datetime "updated_at"
       t.string   "type"
     end
-    
+
     add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree
     add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
     add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree
     add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
-    
+
     create_table "notes", force: true do |t|
       t.text     "note"
       t.string   "noteable_type"
@@ -122,13 +122,13 @@ class InitSchema < ActiveRecord::Migration
       t.string   "commit_id"
       t.integer  "noteable_id"
     end
-    
+
     add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
     add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
     add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
     add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
     add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree
-    
+
     create_table "projects", force: true do |t|
       t.string   "name"
       t.string   "path"
@@ -144,17 +144,17 @@ class InitSchema < ActiveRecord::Migration
       t.boolean  "wiki_enabled",           default: true, null: false
       t.integer  "namespace_id"
     end
-    
+
     add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
     add_index "projects", ["owner_id"], name: "index_projects_on_owner_id", using: :btree
-    
+
     create_table "protected_branches", force: true do |t|
       t.integer  "project_id", null: false
       t.string   "name",       null: false
       t.datetime "created_at"
       t.datetime "updated_at"
     end
-    
+
     create_table "services", force: true do |t|
       t.string   "type"
       t.string   "title"
@@ -165,9 +165,9 @@ class InitSchema < ActiveRecord::Migration
       t.boolean  "active",      default: false, null: false
       t.string   "project_url"
     end
-    
+
     add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
-    
+
     create_table "snippets", force: true do |t|
       t.string   "title"
       t.text     "content"
@@ -178,11 +178,11 @@ class InitSchema < ActiveRecord::Migration
       t.string   "file_name"
       t.datetime "expires_at"
     end
-    
+
     add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
     add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree
     add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
-    
+
     create_table "taggings", force: true do |t|
       t.integer  "tag_id"
       t.integer  "taggable_id"
@@ -192,14 +192,14 @@ class InitSchema < ActiveRecord::Migration
       t.string   "context"
       t.datetime "created_at"
     end
-    
+
     add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree
     add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
-    
+
     create_table "tags", force: true do |t|
       t.string "name"
     end
-    
+
     create_table "user_team_project_relationships", force: true do |t|
       t.integer  "project_id"
       t.integer  "user_team_id"
@@ -207,7 +207,7 @@ class InitSchema < ActiveRecord::Migration
       t.datetime "created_at"
       t.datetime "updated_at"
     end
-    
+
     create_table "user_team_user_relationships", force: true do |t|
       t.integer  "user_id"
       t.integer  "user_team_id"
@@ -216,7 +216,7 @@ class InitSchema < ActiveRecord::Migration
       t.datetime "created_at"
       t.datetime "updated_at"
     end
-    
+
     create_table "user_teams", force: true do |t|
       t.string   "name"
       t.string   "path"
@@ -224,7 +224,7 @@ class InitSchema < ActiveRecord::Migration
       t.datetime "created_at"
       t.datetime "updated_at"
     end
-    
+
     create_table "users", force: true do |t|
       t.string   "email",                  default: "",    null: false
       t.string   "encrypted_password",     default: "",    null: false
@@ -255,7 +255,7 @@ class InitSchema < ActiveRecord::Migration
       t.string   "provider"
       t.string   "username"
     end
-    
+
     add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
     add_index "users", ["blocked"], name: "index_users_on_blocked", using: :btree
     add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
@@ -263,7 +263,7 @@ class InitSchema < ActiveRecord::Migration
     add_index "users", ["name"], name: "index_users_on_name", using: :btree
     add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
     add_index "users", ["username"], name: "index_users_on_username", using: :btree
-    
+
     create_table "users_projects", force: true do |t|
       t.integer  "user_id",                    null: false
       t.integer  "project_id",                 null: false
@@ -271,11 +271,11 @@ class InitSchema < ActiveRecord::Migration
       t.datetime "updated_at"
       t.integer  "project_access", default: 0, null: false
     end
-    
+
     add_index "users_projects", ["project_access"], name: "index_users_projects_on_project_access", using: :btree
     add_index "users_projects", ["project_id"], name: "index_users_projects_on_project_id", using: :btree
     add_index "users_projects", ["user_id"], name: "index_users_projects_on_user_id", using: :btree
-    
+
     create_table "web_hooks", force: true do |t|
       t.string   "url"
       t.integer  "project_id"
@@ -284,7 +284,7 @@ class InitSchema < ActiveRecord::Migration
       t.string   "type",       default: "ProjectHook"
       t.integer  "service_id"
     end
-    
+
     create_table "wikis", force: true do |t|
       t.string   "title"
       t.text     "content"
@@ -294,10 +294,10 @@ class InitSchema < ActiveRecord::Migration
       t.string   "slug"
       t.integer  "user_id"
     end
-    
+
     add_index "wikis", ["project_id"], name: "index_wikis_on_project_id", using: :btree
     add_index "wikis", ["slug"], name: "index_wikis_on_slug", using: :btree
-    
+
   end
 
   def down
diff --git a/db/migrate/20140122112253_create_merge_request_diffs.rb b/db/migrate/20140122112253_create_merge_request_diffs.rb
index ef592305a23a9488f666f8a98d8297cd28d8a815..f34e30925dfd3541af78e7a41fb8849845be2e6a 100644
--- a/db/migrate/20140122112253_create_merge_request_diffs.rb
+++ b/db/migrate/20140122112253_create_merge_request_diffs.rb
@@ -1,12 +1,21 @@
 class CreateMergeRequestDiffs < ActiveRecord::Migration
-  def change
+  def up
     create_table :merge_request_diffs do |t|
       t.string :state, null: false, default: 'collected'
-      t.text :st_commits, null: true, limit: 2147483647
-      t.text :st_diffs, null: true, limit: 2147483647
+      t.text :st_commits, null: true
+      t.text :st_diffs, null: true
       t.integer :merge_request_id, null: false
 
       t.timestamps
     end
+
+    if ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/
+      change_column :merge_request_diffs, :st_commits, :text, limit: 2147483647
+      change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647
+    end
+  end
+
+  def down
+    drop_table :merge_request_diffs
   end
 end
diff --git a/db/migrate/20140903115954_migrate_to_new_shell.rb b/db/migrate/20140903115954_migrate_to_new_shell.rb
index 2d83210951332d9c16ce253831dd5ef596dba6d8..54cbe48960a5a791d096e3fbfcd205d50fc637d0 100644
--- a/db/migrate/20140903115954_migrate_to_new_shell.rb
+++ b/db/migrate/20140903115954_migrate_to_new_shell.rb
@@ -1,5 +1,7 @@
 class MigrateToNewShell < ActiveRecord::Migration
   def change
+    return if Rails.env.test?
+
     gitlab_shell_path = Gitlab.config.gitlab_shell.path
     if system("#{gitlab_shell_path}/bin/create-hooks")
       puts 'Repositories updated with new hooks'
diff --git a/db/migrate/20151012173029_set_jira_service_api_url.rb b/db/migrate/20151012173029_set_jira_service_api_url.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2af99e0db0b3f3624b7ef7866609a247357addc3
--- /dev/null
+++ b/db/migrate/20151012173029_set_jira_service_api_url.rb
@@ -0,0 +1,50 @@
+class SetJiraServiceApiUrl < ActiveRecord::Migration
+  # This migration can be performed online without errors, but some Jira API calls may be missed
+  # when doing so because api_url is not yet available.
+
+  def build_api_url_from_project_url(project_url, api_version)
+    # this is the exact logic previously used to build the Jira API URL from project_url
+    server = URI(project_url)
+    default_ports = [80, 443].include?(server.port)
+    server_url = "#{server.scheme}://#{server.host}"
+    server_url.concat(":#{server.port}") unless default_ports
+    "#{server_url}/rest/api/#{api_version}"
+  end
+
+  def get_api_version_from_api_url(api_url)
+    match = /\/rest\/api\/(?<api_version>\w+)$/.match(api_url)
+    match && match['api_version']
+  end
+
+  def change
+    reversible do |dir|
+      select_all("SELECT id, properties FROM services WHERE services.type IN ('JiraService')").each do |jira_service|
+        id = jira_service["id"]
+        properties = JSON.parse(jira_service["properties"])
+        properties_was = properties.clone
+
+        dir.up do
+          # remove api_version and set api_url
+          if properties['api_version'].present? && properties['project_url'].present?
+            begin
+              properties['api_url'] ||= build_api_url_from_project_url(properties['project_url'], properties['api_version'])
+            rescue
+              # looks like project_url was not a valid URL. Do nothing.
+            end
+          end
+          properties.delete('api_version') if properties.include?('api_version')
+        end
+
+        dir.down do
+          # remove api_url and set api_version (default to '2')
+          properties['api_version'] ||= get_api_version_from_api_url(properties['api_url']) || '2'
+          properties.delete('api_url') if properties.include?('api_url')
+        end
+
+        if properties != properties_was
+          execute("UPDATE services SET properties = '#{quote_string(properties.to_json)}' WHERE id = #{id}")
+        end
+      end
+    end
+  end
+end
diff --git a/db/migrate/20151028152939_add_merge_when_build_succeeds_to_merge_request.rb b/db/migrate/20151028152939_add_merge_when_build_succeeds_to_merge_request.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ceb52f0c2224752e070b03387e7b962e39047766
--- /dev/null
+++ b/db/migrate/20151028152939_add_merge_when_build_succeeds_to_merge_request.rb
@@ -0,0 +1,7 @@
+class AddMergeWhenBuildSucceedsToMergeRequest < ActiveRecord::Migration
+  def change
+    add_column :merge_requests, :merge_params, :text
+    add_column :merge_requests, :merge_when_build_succeeds, :boolean, default: false, null: false
+    add_column :merge_requests, :merge_user_id, :integer
+  end
+end
diff --git a/db/migrate/20151203162133_add_hide_project_limit_to_users.rb b/db/migrate/20151203162133_add_hide_project_limit_to_users.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6ffadfa1894ee71c746cde101e7d95fa26e48682
--- /dev/null
+++ b/db/migrate/20151203162133_add_hide_project_limit_to_users.rb
@@ -0,0 +1,5 @@
+class AddHideProjectLimitToUsers < ActiveRecord::Migration
+  def change
+    add_column :users, :hide_project_limit, :boolean, default: false
+  end
+end
diff --git a/db/migrate/20151203162134_add_build_events_to_services.rb b/db/migrate/20151203162134_add_build_events_to_services.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c5542cb864da8ca5198f8d76dd8ff6fe61d991b5
--- /dev/null
+++ b/db/migrate/20151203162134_add_build_events_to_services.rb
@@ -0,0 +1,6 @@
+class AddBuildEventsToServices < ActiveRecord::Migration
+  def change
+    add_column :services, :build_events, :boolean, default: false, null: false
+    add_column :web_hooks, :build_events, :boolean, default: false, null: false
+  end
+end
diff --git a/db/migrate/20151209144329_migrate_ci_web_hooks.rb b/db/migrate/20151209144329_migrate_ci_web_hooks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d7e196e6763d442f4e650584f0378cb770d75c57
--- /dev/null
+++ b/db/migrate/20151209144329_migrate_ci_web_hooks.rb
@@ -0,0 +1,16 @@
+class MigrateCiWebHooks < ActiveRecord::Migration
+  include Gitlab::Database
+
+  def up
+    execute(
+      'INSERT INTO web_hooks (url, project_id, type, created_at, updated_at, push_events, issues_events, merge_requests_events, tag_push_events, note_events, build_events) ' \
+      "SELECT ci_web_hooks.url, projects.id, 'ProjectHook', ci_web_hooks.created_at, ci_web_hooks.updated_at, " \
+      "#{false_value}, #{false_value}, #{false_value}, #{false_value}, #{false_value}, #{true_value} FROM ci_web_hooks " \
+      'JOIN ci_projects ON ci_web_hooks.project_id = ci_projects.id ' \
+      'JOIN projects ON ci_projects.gitlab_id = projects.id'
+    )
+  end
+
+  def down
+  end
+end
diff --git a/db/migrate/20151209145909_migrate_ci_emails.rb b/db/migrate/20151209145909_migrate_ci_emails.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7f330a2cf0a0fbab8f0a9a6638a0e2596b78cc2a
--- /dev/null
+++ b/db/migrate/20151209145909_migrate_ci_emails.rb
@@ -0,0 +1,45 @@
+class MigrateCiEmails < ActiveRecord::Migration
+  include Gitlab::Database
+
+  def up
+    # This inserts a new service: BuildsEmailService
+    # It "manually" constructs the properties (JSON-encoded)
+    # Migrating all ci_projects e-mail related columns
+    execute(
+      'INSERT INTO services (project_id, type, created_at, updated_at, active, push_events, issues_events, merge_requests_events, tag_push_events, note_events, build_events, properties) ' \
+      "SELECT projects.id, 'BuildsEmailService', ci_services.created_at, ci_services.updated_at, " \
+      "#{true_value}, #{false_value}, #{false_value}, #{false_value}, #{false_value}, #{false_value}, #{true_value}, " \
+      "CONCAT('{\"notify_only_broken_builds\":\"', #{convert_bool('ci_projects.email_only_broken_builds')}, " \
+      "'\",\"add_pusher\":\"', #{convert_bool('ci_projects.email_add_pusher')}, " \
+      "'\",\"recipients\":\"', #{escape_text('ci_projects.email_recipients')}, " \
+      "'\"}') " \
+      'FROM ci_services ' \
+      'JOIN ci_projects ON ci_services.project_id = ci_projects.id ' \
+      'JOIN projects ON ci_projects.gitlab_id = projects.id ' \
+      "WHERE ci_services.type = 'Ci::MailService' AND ci_services.active"
+    )
+  end
+
+  def down
+  end
+
+  # This function escapes double-quotes and slash
+  def escape_text(name)
+    if Gitlab::Database.postgresql?
+      "REPLACE(REPLACE(#{name}, '\\', '\\\\'), '\"', '\\\"')"
+    else
+      "REPLACE(REPLACE(#{name}, '\\\\', '\\\\\\\\'), '\\\"', '\\\\\\\"')"
+    end
+  end
+
+  # This function returns 0 or 1 for column
+  def convert_bool(name)
+    if Gitlab::Database.postgresql?
+      # PostgreSQL uses BOOLEAN type
+      "CASE WHEN #{name} IS TRUE THEN '1' ELSE '0' END"
+    else
+      # MySQL uses TINYINT
+      "#{name}"
+    end
+  end
+end
diff --git a/db/migrate/20151210030143_add_unlock_token_to_user.rb b/db/migrate/20151210030143_add_unlock_token_to_user.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0ea66ba65dfa6df8896b219dcedbb9aac460b9cd
--- /dev/null
+++ b/db/migrate/20151210030143_add_unlock_token_to_user.rb
@@ -0,0 +1,5 @@
+class AddUnlockTokenToUser < ActiveRecord::Migration
+  def change
+    add_column :users, :unlock_token, :string
+  end
+end
diff --git a/db/migrate/20151210072243_add_runners_registration_token_to_application_settings.rb b/db/migrate/20151210072243_add_runners_registration_token_to_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..00f88180e46ab07536689ad373ea221e213e1037
--- /dev/null
+++ b/db/migrate/20151210072243_add_runners_registration_token_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddRunnersRegistrationTokenToApplicationSettings < ActiveRecord::Migration
+  def change
+    add_column :application_settings, :runners_registration_token, :string
+  end
+end
diff --git a/db/migrate/20151210125232_migrate_ci_slack_service.rb b/db/migrate/20151210125232_migrate_ci_slack_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f14efa3e95d398c198ae587665c2f7dba94e5ba1
--- /dev/null
+++ b/db/migrate/20151210125232_migrate_ci_slack_service.rb
@@ -0,0 +1,33 @@
+class MigrateCiSlackService < ActiveRecord::Migration
+  include Gitlab::Database
+
+  def up
+    properties_query = 'SELECT properties FROM ci_services ' \
+      'JOIN ci_projects ON ci_services.project_id=ci_projects.id ' \
+      "WHERE ci_projects.gitlab_id=services.project_id AND ci_services.type='Ci::SlackService' AND ci_services.active " \
+      'LIMIT 1'
+
+    active_query = 'SELECT 1 FROM ci_services ' \
+      'JOIN ci_projects ON ci_services.project_id=ci_projects.id ' \
+      "WHERE ci_projects.gitlab_id=services.project_id AND ci_services.type='Ci::SlackService' AND ci_services.active " \
+      'LIMIT 1'
+
+    # We update the service since services are always generated for project, even if they are inactive
+    # Activate service and migrate properties if currently the service is not active
+    execute(
+      "UPDATE services SET properties=(#{properties_query}), active=#{true_value}, " \
+      "push_events=#{false_value}, issues_events=#{false_value}, merge_requests_events=#{false_value}, " \
+      "tag_push_events=#{false_value}, note_events=#{false_value}, build_events=#{true_value} " \
+      "WHERE NOT services.active AND services.type='SlackService' AND (#{active_query}) IS NOT NULL"
+    )
+
+    # Tick only build_events if the service is already active
+    execute(
+      "UPDATE services SET build_events=#{true_value} " \
+      "WHERE services.active AND services.type='SlackService' AND (#{active_query}) IS NOT NULL"
+    )
+  end
+
+  def down
+  end
+end
diff --git a/db/migrate/20151210125927_migrate_ci_hip_chat_service.rb b/db/migrate/20151210125927_migrate_ci_hip_chat_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b9e04323576eabe1d50b306d032cf6763c244142
--- /dev/null
+++ b/db/migrate/20151210125927_migrate_ci_hip_chat_service.rb
@@ -0,0 +1,34 @@
+class MigrateCiHipChatService < ActiveRecord::Migration
+  include Gitlab::Database
+
+  def up
+    # From properties strip `hipchat_` key
+    properties_query = "SELECT REPLACE(properties, '\"hipchat_', '\"') FROM ci_services " \
+      'JOIN ci_projects ON ci_services.project_id=ci_projects.id ' \
+      "WHERE ci_projects.gitlab_id=services.project_id AND ci_services.type='Ci::HipChatService' AND ci_services.active " \
+      'LIMIT 1'
+
+    active_query = 'SELECT 1 FROM ci_services ' \
+      'JOIN ci_projects ON ci_services.project_id=ci_projects.id ' \
+      "WHERE ci_projects.gitlab_id=services.project_id AND ci_services.type='Ci::HipChatService' AND ci_services.active " \
+      'LIMIT 1'
+
+    # We update the service since services are always generated for project, even if they are inactive
+    # Activate service and migrate properties if currently the service is not active
+    execute(
+      "UPDATE services SET properties=(#{properties_query}), active=#{true_value}, " \
+      "push_events=#{false_value}, issues_events=#{false_value}, merge_requests_events=#{false_value}, " \
+      "tag_push_events=#{false_value}, note_events=#{false_value}, build_events=#{true_value} " \
+      "WHERE NOT services.active AND services.type='HipchatService' AND (#{active_query}) IS NOT NULL"
+    )
+
+    # Tick only build_events if the service is already active
+    execute(
+      "UPDATE services SET build_events=#{true_value} " \
+      "WHERE services.active AND services.type='HipchatService' AND (#{active_query}) IS NOT NULL"
+    )
+  end
+
+  def down
+  end
+end
diff --git a/db/migrate/20151210125928_add_ci_to_project.rb b/db/migrate/20151210125928_add_ci_to_project.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8c167f64a2b59444c94a5b8a1f914a252e44aec1
--- /dev/null
+++ b/db/migrate/20151210125928_add_ci_to_project.rb
@@ -0,0 +1,11 @@
+class AddCiToProject < ActiveRecord::Migration
+  def change
+    add_column :projects, :ci_id, :integer
+    add_column :projects, :builds_enabled, :boolean, default: true, null: false
+    add_column :projects, :shared_runners_enabled, :boolean, default: true, null: false
+    add_column :projects, :runners_token, :string
+    add_column :projects, :build_coverage_regex, :string
+    add_column :projects, :build_allow_git_fetch, :boolean, default: true, null: false
+    add_column :projects, :build_timeout, :integer, default: 3600, null: false
+  end
+end
diff --git a/db/migrate/20151210125929_add_project_id_to_ci.rb b/db/migrate/20151210125929_add_project_id_to_ci.rb
new file mode 100644
index 0000000000000000000000000000000000000000..84273591fa2ede4f606e2ab9cae2418330e00cf1
--- /dev/null
+++ b/db/migrate/20151210125929_add_project_id_to_ci.rb
@@ -0,0 +1,8 @@
+class AddProjectIdToCi < ActiveRecord::Migration
+  def change
+    add_column :ci_builds, :gl_project_id, :integer
+    add_column :ci_runner_projects, :gl_project_id, :integer
+    add_column :ci_triggers, :gl_project_id, :integer
+    add_column :ci_variables, :gl_project_id, :integer
+  end
+end
diff --git a/db/migrate/20151210125930_migrate_ci_to_project.rb b/db/migrate/20151210125930_migrate_ci_to_project.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c32c7feb1931443f5e28b1ee0d56eae5cbe71674
--- /dev/null
+++ b/db/migrate/20151210125930_migrate_ci_to_project.rb
@@ -0,0 +1,42 @@
+class MigrateCiToProject < ActiveRecord::Migration
+  def up
+    migrate_project_id_for_table('ci_runner_projects')
+    migrate_project_id_for_table('ci_triggers')
+    migrate_project_id_for_table('ci_variables')
+    migrate_project_id_for_builds
+
+    migrate_project_column('id', 'ci_id')
+    migrate_project_column('shared_runners_enabled', 'shared_runners_enabled')
+    migrate_project_column('token', 'runners_token')
+    migrate_project_column('coverage_regex', 'build_coverage_regex')
+    migrate_project_column('allow_git_fetch', 'build_allow_git_fetch')
+    migrate_project_column('timeout', 'build_timeout')
+    migrate_ci_service
+  end
+
+  def down
+    # We can't reverse the data
+  end
+
+  def migrate_project_id_for_table(table)
+    subquery = "SELECT gitlab_id FROM ci_projects WHERE ci_projects.id = #{table}.project_id"
+    execute("UPDATE #{table} SET gl_project_id=(#{subquery}) WHERE gl_project_id IS NULL")
+  end
+
+  def migrate_project_id_for_builds
+    subquery = 'SELECT gl_project_id FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id'
+    execute("UPDATE ci_builds SET gl_project_id=(#{subquery}) WHERE gl_project_id IS NULL")
+  end
+
+  def migrate_project_column(column, new_column = nil)
+    new_column ||= column
+    subquery = "SELECT ci_projects.#{column} FROM ci_projects WHERE projects.id = ci_projects.gitlab_id " \
+      'ORDER BY ci_projects.updated_at DESC LIMIT 1'
+    execute("UPDATE projects SET #{new_column}=(#{subquery}) WHERE (#{subquery}) IS NOT NULL")
+  end
+
+  def migrate_ci_service
+    subquery = "SELECT active FROM services WHERE projects.id = services.project_id AND type='GitlabCiService' LIMIT 1"
+    execute("UPDATE projects SET builds_enabled=(#{subquery}) WHERE (#{subquery}) IS NOT NULL")
+  end
+end
diff --git a/db/migrate/20151210125931_add_index_to_ci_tables.rb b/db/migrate/20151210125931_add_index_to_ci_tables.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5e129c9303d9b0bece0d8fc36100682f8a8a969f
--- /dev/null
+++ b/db/migrate/20151210125931_add_index_to_ci_tables.rb
@@ -0,0 +1,12 @@
+class AddIndexToCiTables < ActiveRecord::Migration
+  def change
+    add_index :ci_builds, :gl_project_id
+    add_index :ci_runner_projects, :gl_project_id
+    add_index :ci_triggers, :gl_project_id
+    add_index :ci_variables, :gl_project_id
+    add_index :projects, :runners_token
+    add_index :projects, :builds_enabled
+    add_index :projects, [:builds_enabled, :shared_runners_enabled]
+    add_index :projects, [:ci_id]
+  end
+end
diff --git a/db/migrate/20151210125932_drop_null_for_ci_tables.rb b/db/migrate/20151210125932_drop_null_for_ci_tables.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c520c2ed56f67e2f08d91fa29174d12bcb413ece
--- /dev/null
+++ b/db/migrate/20151210125932_drop_null_for_ci_tables.rb
@@ -0,0 +1,9 @@
+class DropNullForCiTables < ActiveRecord::Migration
+  def change
+    remove_index :ci_variables, :project_id
+    remove_index :ci_runner_projects, :project_id
+    change_column_null :ci_triggers, :project_id, true
+    change_column_null :ci_variables, :project_id, true
+    change_column_null :ci_runner_projects, :project_id, true
+  end
+end
diff --git a/db/migrate/20151218154042_add_tfa_to_application_settings.rb b/db/migrate/20151218154042_add_tfa_to_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dd95db775c5e5e548b8d567223fd8c9dc2607de0
--- /dev/null
+++ b/db/migrate/20151218154042_add_tfa_to_application_settings.rb
@@ -0,0 +1,8 @@
+class AddTfaToApplicationSettings < ActiveRecord::Migration
+  def change
+    change_table :application_settings do |t|
+      t.boolean :require_two_factor_authentication, default: false
+      t.integer :two_factor_grace_period, default: 48
+    end
+  end
+end
diff --git a/db/migrate/20151221234414_add_tfa_additional_fields.rb b/db/migrate/20151221234414_add_tfa_additional_fields.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c16df47932f6e902d58e5758bc5ff86047cfdfc8
--- /dev/null
+++ b/db/migrate/20151221234414_add_tfa_additional_fields.rb
@@ -0,0 +1,7 @@
+class AddTfaAdditionalFields < ActiveRecord::Migration
+  def change
+    change_table :users do |t|
+      t.datetime :otp_grace_period_started_at, null: true
+    end
+  end
+end
diff --git a/db/migrate/20151224123230_rename_emojis.rb b/db/migrate/20151224123230_rename_emojis.rb
new file mode 100644
index 0000000000000000000000000000000000000000..62d921dfdcce3e26b7efd0afd2a3dd192bddeead
--- /dev/null
+++ b/db/migrate/20151224123230_rename_emojis.rb
@@ -0,0 +1,15 @@
+# Migration type: online without errors (works on previous version and new one)
+class RenameEmojis < ActiveRecord::Migration
+  def up
+    # Renames aliases to main names
+    execute("UPDATE notes SET note ='thumbsup' WHERE is_award = true AND note = '+1'")
+    execute("UPDATE notes SET note ='thumbsdown' WHERE is_award = true AND note = '-1'")
+    execute("UPDATE notes SET note ='poop' WHERE is_award = true AND note = 'shit'")
+  end
+
+  def down
+    execute("UPDATE notes SET note ='+1' WHERE is_award = true AND note = 'thumbsup'")
+    execute("UPDATE notes SET note ='-1' WHERE is_award = true AND note = 'thumbsdown'")
+    execute("UPDATE notes SET note ='shit' WHERE is_award = true AND note = 'poop'")
+  end
+end
diff --git a/db/migrate/20151228150906_influxdb_settings.rb b/db/migrate/20151228150906_influxdb_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3012bd52cfdd96c09ed4bd9cdd13996e41e65d1b
--- /dev/null
+++ b/db/migrate/20151228150906_influxdb_settings.rb
@@ -0,0 +1,18 @@
+class InfluxdbSettings < ActiveRecord::Migration
+  def change
+    add_column :application_settings, :metrics_enabled, :boolean, default: false
+
+    add_column :application_settings, :metrics_host, :string,
+      default: 'localhost'
+
+    add_column :application_settings, :metrics_database, :string,
+      default: 'gitlab'
+
+    add_column :application_settings, :metrics_username, :string
+    add_column :application_settings, :metrics_password, :string
+    add_column :application_settings, :metrics_pool_size, :integer, default: 16
+    add_column :application_settings, :metrics_timeout, :integer, default: 10
+    add_column :application_settings, :metrics_method_call_threshold,
+      :integer, default: 10
+  end
+end
diff --git a/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb b/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..259fd0248d2914c52f95a8353668458b8d0d9ce7
--- /dev/null
+++ b/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb
@@ -0,0 +1,9 @@
+class AddRecaptchaToApplicationSettings < ActiveRecord::Migration
+  def change
+    change_table :application_settings do |t|
+      t.boolean :recaptcha_enabled, default: false
+      t.string :recaptcha_site_key
+      t.string :recaptcha_private_key
+    end
+  end
+end
diff --git a/db/migrate/20151229102248_influxdb_udp_port_setting.rb b/db/migrate/20151229102248_influxdb_udp_port_setting.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ae0499f936d6fcfd719c3c231e2ecabf975c4f26
--- /dev/null
+++ b/db/migrate/20151229102248_influxdb_udp_port_setting.rb
@@ -0,0 +1,5 @@
+class InfluxdbUdpPortSetting < ActiveRecord::Migration
+  def change
+    add_column :application_settings, :metrics_port, :integer, default: 8089
+  end
+end
diff --git a/db/migrate/20151229112614_influxdb_remote_database_setting.rb b/db/migrate/20151229112614_influxdb_remote_database_setting.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f0e1ee1e7a79a0d3720160796fc1ffa389039fc8
--- /dev/null
+++ b/db/migrate/20151229112614_influxdb_remote_database_setting.rb
@@ -0,0 +1,5 @@
+class InfluxdbRemoteDatabaseSetting < ActiveRecord::Migration
+  def change
+    remove_column :application_settings, :metrics_database
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5bbe0c908ef6f5265782c4c99577c3d6171e7d55..df7f72d5ad4a7591dde54f7a9cdc18fc9a8b0871 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,12 +11,12 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20151118162244) do
+ActiveRecord::Schema.define(version: 20151229112614) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
 
-  create_table "abuse_reports", force: true do |t|
+  create_table "abuse_reports", force: :cascade do |t|
     t.integer  "reporter_id"
     t.integer  "user_id"
     t.text     "message"
@@ -24,7 +24,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
     t.datetime "updated_at"
   end
 
-  create_table "application_settings", force: true do |t|
+  create_table "application_settings", force: :cascade do |t|
     t.integer  "default_projects_limit"
     t.boolean  "signup_enabled"
     t.boolean  "signin_enabled"
@@ -33,25 +33,39 @@ ActiveRecord::Schema.define(version: 20151118162244) do
     t.datetime "created_at"
     t.datetime "updated_at"
     t.string   "home_page_url"
-    t.integer  "default_branch_protection",    default: 2
-    t.boolean  "twitter_sharing_enabled",      default: true
+    t.integer  "default_branch_protection",         default: 2
+    t.boolean  "twitter_sharing_enabled",           default: true
     t.text     "restricted_visibility_levels"
-    t.boolean  "version_check_enabled",        default: true
-    t.integer  "max_attachment_size",          default: 10,    null: false
+    t.boolean  "version_check_enabled",             default: true
+    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.boolean  "user_oauth_applications",      default: true
+    t.boolean  "user_oauth_applications",           default: true
     t.string   "after_sign_out_path"
-    t.integer  "session_expire_delay",         default: 10080, null: false
+    t.integer  "session_expire_delay",              default: 10080,       null: false
     t.text     "import_sources"
     t.text     "help_page_text"
     t.string   "admin_notification_email"
-    t.boolean  "shared_runners_enabled",       default: true,  null: false
-    t.integer  "max_artifacts_size",           default: 100,   null: false
-  end
-
-  create_table "audit_events", force: true do |t|
+    t.boolean  "shared_runners_enabled",            default: true,        null: false
+    t.integer  "max_artifacts_size",                default: 100,         null: false
+    t.string   "runners_registration_token"
+    t.boolean  "require_two_factor_authentication", default: false
+    t.integer  "two_factor_grace_period",           default: 48
+    t.boolean  "metrics_enabled",                   default: false
+    t.string   "metrics_host",                      default: "localhost"
+    t.string   "metrics_username"
+    t.string   "metrics_password"
+    t.integer  "metrics_pool_size",                 default: 16
+    t.integer  "metrics_timeout",                   default: 10
+    t.integer  "metrics_method_call_threshold",     default: 10
+    t.boolean  "recaptcha_enabled",                 default: false
+    t.string   "recaptcha_site_key"
+    t.string   "recaptcha_private_key"
+    t.integer  "metrics_port",                      default: 8089
+  end
+
+  create_table "audit_events", force: :cascade do |t|
     t.integer  "author_id",   null: false
     t.string   "type",        null: false
     t.integer  "entity_id",   null: false
@@ -65,7 +79,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
   add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree
 
-  create_table "broadcast_messages", force: true do |t|
+  create_table "broadcast_messages", force: :cascade do |t|
     t.text     "message",    null: false
     t.datetime "starts_at"
     t.datetime "ends_at"
@@ -76,14 +90,14 @@ ActiveRecord::Schema.define(version: 20151118162244) do
     t.string   "font"
   end
 
-  create_table "ci_application_settings", force: true do |t|
+  create_table "ci_application_settings", force: :cascade do |t|
     t.boolean  "all_broken_builds"
     t.boolean  "add_pusher"
     t.datetime "created_at"
     t.datetime "updated_at"
   end
 
-  create_table "ci_builds", force: true do |t|
+  create_table "ci_builds", force: :cascade do |t|
     t.integer  "project_id"
     t.string   "status"
     t.datetime "finished_at"
@@ -110,6 +124,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
     t.string   "target_url"
     t.string   "description"
     t.text     "artifacts_file"
+    t.integer  "gl_project_id"
   end
 
   add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
@@ -117,13 +132,14 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
   add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
   add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree
+  add_index "ci_builds", ["gl_project_id"], name: "index_ci_builds_on_gl_project_id", using: :btree
   add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree
   add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
   add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
   add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
   add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree
 
-  create_table "ci_commits", force: true do |t|
+  create_table "ci_commits", force: :cascade do |t|
     t.integer  "project_id"
     t.string   "ref"
     t.string   "sha"
@@ -144,7 +160,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree
   add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree
 
-  create_table "ci_events", force: true do |t|
+  create_table "ci_events", force: :cascade do |t|
     t.integer  "project_id"
     t.integer  "user_id"
     t.integer  "is_admin"
@@ -157,7 +173,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "ci_events", ["is_admin"], name: "index_ci_events_on_is_admin", using: :btree
   add_index "ci_events", ["project_id"], name: "index_ci_events_on_project_id", using: :btree
 
-  create_table "ci_jobs", force: true do |t|
+  create_table "ci_jobs", force: :cascade do |t|
     t.integer  "project_id",                          null: false
     t.text     "commands"
     t.boolean  "active",         default: true,       null: false
@@ -174,7 +190,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "ci_jobs", ["deleted_at"], name: "index_ci_jobs_on_deleted_at", using: :btree
   add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree
 
-  create_table "ci_projects", force: true do |t|
+  create_table "ci_projects", force: :cascade do |t|
     t.string   "name"
     t.integer  "timeout",                  default: 3600,  null: false
     t.datetime "created_at"
@@ -200,17 +216,18 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "ci_projects", ["gitlab_id"], name: "index_ci_projects_on_gitlab_id", using: :btree
   add_index "ci_projects", ["shared_runners_enabled"], name: "index_ci_projects_on_shared_runners_enabled", using: :btree
 
-  create_table "ci_runner_projects", force: true do |t|
-    t.integer  "runner_id",  null: false
-    t.integer  "project_id", null: false
+  create_table "ci_runner_projects", force: :cascade do |t|
+    t.integer  "runner_id",     null: false
+    t.integer  "project_id"
     t.datetime "created_at"
     t.datetime "updated_at"
+    t.integer  "gl_project_id"
   end
 
-  add_index "ci_runner_projects", ["project_id"], name: "index_ci_runner_projects_on_project_id", using: :btree
+  add_index "ci_runner_projects", ["gl_project_id"], name: "index_ci_runner_projects_on_gl_project_id", using: :btree
   add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree
 
-  create_table "ci_runners", force: true do |t|
+  create_table "ci_runners", force: :cascade do |t|
     t.string   "token"
     t.datetime "created_at"
     t.datetime "updated_at"
@@ -225,7 +242,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
     t.string   "architecture"
   end
 
-  create_table "ci_services", force: true do |t|
+  create_table "ci_services", force: :cascade do |t|
     t.string   "type"
     t.string   "title"
     t.integer  "project_id",                 null: false
@@ -237,7 +254,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
 
   add_index "ci_services", ["project_id"], name: "index_ci_services_on_project_id", using: :btree
 
-  create_table "ci_sessions", force: true do |t|
+  create_table "ci_sessions", force: :cascade do |t|
     t.string   "session_id", null: false
     t.text     "data"
     t.datetime "created_at"
@@ -247,7 +264,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "ci_sessions", ["session_id"], name: "index_ci_sessions_on_session_id", using: :btree
   add_index "ci_sessions", ["updated_at"], name: "index_ci_sessions_on_updated_at", using: :btree
 
-  create_table "ci_taggings", force: true do |t|
+  create_table "ci_taggings", force: :cascade do |t|
     t.integer  "tag_id"
     t.integer  "taggable_id"
     t.string   "taggable_type"
@@ -260,14 +277,14 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "ci_taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "ci_taggings_idx", unique: true, using: :btree
   add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
 
-  create_table "ci_tags", force: true do |t|
+  create_table "ci_tags", force: :cascade do |t|
     t.string  "name"
     t.integer "taggings_count", default: 0
   end
 
   add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree
 
-  create_table "ci_trigger_requests", force: true do |t|
+  create_table "ci_trigger_requests", force: :cascade do |t|
     t.integer  "trigger_id", null: false
     t.text     "variables"
     t.datetime "created_at"
@@ -275,35 +292,38 @@ ActiveRecord::Schema.define(version: 20151118162244) do
     t.integer  "commit_id"
   end
 
-  create_table "ci_triggers", force: true do |t|
+  create_table "ci_triggers", force: :cascade do |t|
     t.string   "token"
-    t.integer  "project_id", null: false
+    t.integer  "project_id"
     t.datetime "deleted_at"
     t.datetime "created_at"
     t.datetime "updated_at"
+    t.integer  "gl_project_id"
   end
 
   add_index "ci_triggers", ["deleted_at"], name: "index_ci_triggers_on_deleted_at", using: :btree
+  add_index "ci_triggers", ["gl_project_id"], name: "index_ci_triggers_on_gl_project_id", using: :btree
 
-  create_table "ci_variables", force: true do |t|
-    t.integer "project_id",           null: false
+  create_table "ci_variables", force: :cascade do |t|
+    t.integer "project_id"
     t.string  "key"
     t.text    "value"
     t.text    "encrypted_value"
     t.string  "encrypted_value_salt"
     t.string  "encrypted_value_iv"
+    t.integer "gl_project_id"
   end
 
-  add_index "ci_variables", ["project_id"], name: "index_ci_variables_on_project_id", using: :btree
+  add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree
 
-  create_table "ci_web_hooks", force: true do |t|
+  create_table "ci_web_hooks", force: :cascade do |t|
     t.string   "url",        null: false
     t.integer  "project_id", null: false
     t.datetime "created_at"
     t.datetime "updated_at"
   end
 
-  create_table "deploy_keys_projects", force: true do |t|
+  create_table "deploy_keys_projects", force: :cascade do |t|
     t.integer  "deploy_key_id", null: false
     t.integer  "project_id",    null: false
     t.datetime "created_at"
@@ -312,7 +332,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
 
   add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
 
-  create_table "emails", force: true do |t|
+  create_table "emails", force: :cascade do |t|
     t.integer  "user_id",    null: false
     t.string   "email",      null: false
     t.datetime "created_at"
@@ -322,7 +342,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree
   add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree
 
-  create_table "events", force: true do |t|
+  create_table "events", force: :cascade do |t|
     t.string   "target_type"
     t.integer  "target_id"
     t.string   "title"
@@ -341,7 +361,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "events", ["target_id"], name: "index_events_on_target_id", using: :btree
   add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree
 
-  create_table "forked_project_links", force: true do |t|
+  create_table "forked_project_links", force: :cascade do |t|
     t.integer  "forked_to_project_id",   null: false
     t.integer  "forked_from_project_id", null: false
     t.datetime "created_at"
@@ -350,7 +370,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
 
   add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
 
-  create_table "identities", force: true do |t|
+  create_table "identities", force: :cascade do |t|
     t.string   "extern_uid"
     t.string   "provider"
     t.integer  "user_id"
@@ -361,7 +381,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "identities", ["created_at", "id"], name: "index_identities_on_created_at_and_id", using: :btree
   add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
 
-  create_table "issues", force: true do |t|
+  create_table "issues", force: :cascade do |t|
     t.string   "title"
     t.integer  "assignee_id"
     t.integer  "author_id"
@@ -387,7 +407,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "issues", ["state"], name: "index_issues_on_state", using: :btree
   add_index "issues", ["title"], name: "index_issues_on_title", using: :btree
 
-  create_table "keys", force: true do |t|
+  create_table "keys", force: :cascade do |t|
     t.integer  "user_id"
     t.datetime "created_at"
     t.datetime "updated_at"
@@ -401,7 +421,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree
   add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree
 
-  create_table "label_links", force: true do |t|
+  create_table "label_links", force: :cascade do |t|
     t.integer  "label_id"
     t.integer  "target_id"
     t.string   "target_type"
@@ -412,7 +432,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "label_links", ["label_id"], name: "index_label_links_on_label_id", using: :btree
   add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree
 
-  create_table "labels", force: true do |t|
+  create_table "labels", force: :cascade do |t|
     t.string   "title"
     t.string   "color"
     t.integer  "project_id"
@@ -423,7 +443,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
 
   add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
 
-  create_table "lfs_objects", force: true do |t|
+  create_table "lfs_objects", force: :cascade do |t|
     t.string   "oid",        null: false
     t.integer  "size",       null: false
     t.datetime "created_at"
@@ -433,7 +453,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
 
   add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree
 
-  create_table "lfs_objects_projects", force: true do |t|
+  create_table "lfs_objects_projects", force: :cascade do |t|
     t.integer  "lfs_object_id", null: false
     t.integer  "project_id",    null: false
     t.datetime "created_at"
@@ -442,7 +462,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
 
   add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree
 
-  create_table "members", force: true do |t|
+  create_table "members", force: :cascade do |t|
     t.integer  "access_level",       null: false
     t.integer  "source_id",          null: false
     t.string   "source_type",        null: false
@@ -464,7 +484,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "members", ["type"], name: "index_members_on_type", using: :btree
   add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree
 
-  create_table "merge_request_diffs", force: true do |t|
+  create_table "merge_request_diffs", force: :cascade do |t|
     t.string   "state"
     t.text     "st_commits"
     t.text     "st_diffs"
@@ -475,10 +495,10 @@ ActiveRecord::Schema.define(version: 20151118162244) 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: true do |t|
-    t.string   "target_branch",                 null: false
-    t.string   "source_branch",                 null: false
-    t.integer  "source_project_id",             null: false
+  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.integer  "author_id"
     t.integer  "assignee_id"
     t.string   "title"
@@ -487,13 +507,16 @@ ActiveRecord::Schema.define(version: 20151118162244) 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.integer  "merge_user_id"
   end
 
   add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
@@ -507,7 +530,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree
   add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree
 
-  create_table "milestones", force: true do |t|
+  create_table "milestones", force: :cascade do |t|
     t.string   "title",       null: false
     t.integer  "project_id",  null: false
     t.text     "description"
@@ -523,7 +546,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree
   add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree
 
-  create_table "namespaces", force: true do |t|
+  create_table "namespaces", force: :cascade do |t|
     t.string   "name",                        null: false
     t.string   "path",                        null: false
     t.integer  "owner_id"
@@ -542,7 +565,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "namespaces", ["public"], name: "index_namespaces_on_public", using: :btree
   add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
 
-  create_table "notes", force: true do |t|
+  create_table "notes", force: :cascade do |t|
     t.text     "note"
     t.string   "noteable_type"
     t.integer  "author_id"
@@ -571,7 +594,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree
   add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree
 
-  create_table "oauth_access_grants", force: true do |t|
+  create_table "oauth_access_grants", force: :cascade do |t|
     t.integer  "resource_owner_id", null: false
     t.integer  "application_id",    null: false
     t.string   "token",             null: false
@@ -584,7 +607,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
 
   add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree
 
-  create_table "oauth_access_tokens", force: true do |t|
+  create_table "oauth_access_tokens", force: :cascade do |t|
     t.integer  "resource_owner_id"
     t.integer  "application_id"
     t.string   "token",             null: false
@@ -599,7 +622,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "oauth_access_tokens", ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", using: :btree
   add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree
 
-  create_table "oauth_applications", force: true do |t|
+  create_table "oauth_applications", force: :cascade do |t|
     t.string   "name",                      null: false
     t.string   "uid",                       null: false
     t.string   "secret",                    null: false
@@ -614,12 +637,12 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree
   add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
 
-  create_table "project_import_data", force: true do |t|
+  create_table "project_import_data", force: :cascade do |t|
     t.integer "project_id"
     t.text    "data"
   end
 
-  create_table "projects", force: true do |t|
+  create_table "projects", force: :cascade do |t|
     t.string   "name"
     t.string   "path"
     t.text     "description"
@@ -646,17 +669,28 @@ ActiveRecord::Schema.define(version: 20151118162244) do
     t.string   "import_source"
     t.integer  "commit_count",           default: 0
     t.text     "import_error"
-  end
-
+    t.integer  "ci_id"
+    t.boolean  "builds_enabled",         default: true,     null: false
+    t.boolean  "shared_runners_enabled", default: true,     null: false
+    t.string   "runners_token"
+    t.string   "build_coverage_regex"
+    t.boolean  "build_allow_git_fetch",  default: true,     null: false
+    t.integer  "build_timeout",          default: 3600,     null: false
+  end
+
+  add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
+  add_index "projects", ["builds_enabled"], name: "index_projects_on_builds_enabled", using: :btree
+  add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
   add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
   add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree
   add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree
   add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
   add_index "projects", ["path"], name: "index_projects_on_path", using: :btree
+  add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree
   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_branches", force: true do |t|
+  create_table "protected_branches", force: :cascade do |t|
     t.integer  "project_id",                          null: false
     t.string   "name",                                null: false
     t.datetime "created_at"
@@ -666,7 +700,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
 
   add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
 
-  create_table "releases", force: true do |t|
+  create_table "releases", force: :cascade do |t|
     t.string   "tag"
     t.text     "description"
     t.integer  "project_id"
@@ -677,7 +711,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree
   add_index "releases", ["project_id"], name: "index_releases_on_project_id", using: :btree
 
-  create_table "sent_notifications", force: true do |t|
+  create_table "sent_notifications", force: :cascade do |t|
     t.integer "project_id"
     t.integer "noteable_id"
     t.string  "noteable_type"
@@ -689,7 +723,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
 
   add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree
 
-  create_table "services", force: true do |t|
+  create_table "services", force: :cascade do |t|
     t.string   "type"
     t.string   "title"
     t.integer  "project_id"
@@ -703,13 +737,14 @@ ActiveRecord::Schema.define(version: 20151118162244) do
     t.boolean  "merge_requests_events", default: true
     t.boolean  "tag_push_events",       default: true
     t.boolean  "note_events",           default: true,  null: false
+    t.boolean  "build_events",          default: false, null: false
   end
 
   add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree
   add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
   add_index "services", ["template"], name: "index_services_on_template", using: :btree
 
-  create_table "snippets", force: true do |t|
+  create_table "snippets", force: :cascade do |t|
     t.string   "title"
     t.text     "content"
     t.integer  "author_id",                    null: false
@@ -729,7 +764,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
   add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
 
-  create_table "subscriptions", force: true do |t|
+  create_table "subscriptions", force: :cascade do |t|
     t.integer  "user_id"
     t.integer  "subscribable_id"
     t.string   "subscribable_type"
@@ -740,7 +775,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
 
   add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id"], name: "subscriptions_user_id_and_ref_fields", unique: true, using: :btree
 
-  create_table "taggings", force: true do |t|
+  create_table "taggings", force: :cascade do |t|
     t.integer  "tag_id"
     t.integer  "taggable_id"
     t.string   "taggable_type"
@@ -753,20 +788,20 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree
   add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
 
-  create_table "tags", force: true do |t|
+  create_table "tags", force: :cascade do |t|
     t.string  "name"
     t.integer "taggings_count", default: 0
   end
 
   add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree
 
-  create_table "users", force: true do |t|
-    t.string   "email",                      default: "",    null: false
-    t.string   "encrypted_password",         default: "",    null: false
+  create_table "users", force: :cascade do |t|
+    t.string   "email",                       default: "",    null: false
+    t.string   "encrypted_password",          default: "",    null: false
     t.string   "reset_password_token"
     t.datetime "reset_password_sent_at"
     t.datetime "remember_created_at"
-    t.integer  "sign_in_count",              default: 0
+    t.integer  "sign_in_count",               default: 0
     t.datetime "current_sign_in_at"
     t.datetime "last_sign_in_at"
     t.string   "current_sign_in_ip"
@@ -774,22 +809,22 @@ ActiveRecord::Schema.define(version: 20151118162244) do
     t.datetime "created_at"
     t.datetime "updated_at"
     t.string   "name"
-    t.boolean  "admin",                      default: false, null: false
-    t.integer  "projects_limit",             default: 10
-    t.string   "skype",                      default: "",    null: false
-    t.string   "linkedin",                   default: "",    null: false
-    t.string   "twitter",                    default: "",    null: false
+    t.boolean  "admin",                       default: false, null: false
+    t.integer  "projects_limit",              default: 10
+    t.string   "skype",                       default: "",    null: false
+    t.string   "linkedin",                    default: "",    null: false
+    t.string   "twitter",                     default: "",    null: false
     t.string   "authentication_token"
-    t.integer  "theme_id",                   default: 1,     null: false
+    t.integer  "theme_id",                    default: 1,     null: false
     t.string   "bio"
-    t.integer  "failed_attempts",            default: 0
+    t.integer  "failed_attempts",             default: 0
     t.datetime "locked_at"
     t.string   "username"
-    t.boolean  "can_create_group",           default: true,  null: false
-    t.boolean  "can_create_team",            default: true,  null: false
+    t.boolean  "can_create_group",            default: true,  null: false
+    t.boolean  "can_create_team",             default: true,  null: false
     t.string   "state"
-    t.integer  "color_scheme_id",            default: 1,     null: false
-    t.integer  "notification_level",         default: 1,     null: false
+    t.integer  "color_scheme_id",             default: 1,     null: false
+    t.integer  "notification_level",          default: 1,     null: false
     t.datetime "password_expires_at"
     t.integer  "created_by_id"
     t.datetime "last_credential_check_at"
@@ -798,22 +833,25 @@ ActiveRecord::Schema.define(version: 20151118162244) do
     t.datetime "confirmed_at"
     t.datetime "confirmation_sent_at"
     t.string   "unconfirmed_email"
-    t.boolean  "hide_no_ssh_key",            default: false
-    t.string   "website_url",                default: "",    null: false
+    t.boolean  "hide_no_ssh_key",             default: false
+    t.string   "website_url",                 default: "",    null: false
     t.string   "notification_email"
-    t.boolean  "hide_no_password",           default: false
-    t.boolean  "password_automatically_set", default: false
+    t.boolean  "hide_no_password",            default: false
+    t.boolean  "password_automatically_set",  default: false
     t.string   "location"
     t.string   "encrypted_otp_secret"
     t.string   "encrypted_otp_secret_iv"
     t.string   "encrypted_otp_secret_salt"
-    t.boolean  "otp_required_for_login",     default: false, null: false
+    t.boolean  "otp_required_for_login",      default: false, null: false
     t.text     "otp_backup_codes"
-    t.string   "public_email",               default: "",    null: false
-    t.integer  "dashboard",                  default: 0
-    t.integer  "project_view",               default: 0
+    t.string   "public_email",                default: "",    null: false
+    t.integer  "dashboard",                   default: 0
+    t.integer  "project_view",                default: 0
     t.integer  "consumed_timestep"
-    t.integer  "layout",                     default: 0
+    t.integer  "layout",                      default: 0
+    t.boolean  "hide_project_limit",          default: false
+    t.string   "unlock_token"
+    t.datetime "otp_grace_period_started_at"
   end
 
   add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
@@ -826,7 +864,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
   add_index "users", ["username"], name: "index_users_on_username", using: :btree
 
-  create_table "users_star_projects", force: true do |t|
+  create_table "users_star_projects", force: :cascade do |t|
     t.integer  "project_id", null: false
     t.integer  "user_id",    null: false
     t.datetime "created_at"
@@ -837,7 +875,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
   add_index "users_star_projects", ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true, using: :btree
   add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree
 
-  create_table "web_hooks", force: true do |t|
+  create_table "web_hooks", force: :cascade do |t|
     t.string   "url"
     t.integer  "project_id"
     t.datetime "created_at"
@@ -850,6 +888,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
     t.boolean  "tag_push_events",         default: false
     t.boolean  "note_events",             default: false,         null: false
     t.boolean  "enable_ssl_verification", default: true
+    t.boolean  "build_events",            default: false,         null: false
   end
 
   add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
diff --git a/doc/README.md b/doc/README.md
index 58ab5dd08e0b2771a84285e65762d2dead3122a4..d82ff8b908be8eb406cf6d5f6f6ca00d5513ce83 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -7,6 +7,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).
 - [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
+- [Migrating from SVN](migration/README.md) Convert a SVN repository to Git and GitLab
 - [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
 - [Profile Settings](profile/README.md)
 - [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
@@ -24,8 +25,21 @@
 - [Using Docker Images](ci/docker/using_docker_images.md)
 - [Using Docker Build](ci/docker/using_docker_build.md)
 - [Using Variables](ci/variables/README.md)
+- [Using SSH keys](ci/ssh_keys/README.md)
 - [User permissions](ci/permissions/README.md)
 - [API](ci/api/README.md)
+- [Triggering builds through the API](ci/triggers/README.md)
+
+### CI Languages
+
+- [Testing PHP](ci/languages/php.md)
+
+### CI Services
+
+- [Using MySQL](ci/services/mysql.md)
+- [Using PostgreSQL](ci/services/postgres.md)
+- [Using Redis](ci/services/redis.md)
+- [Using Other Services](ci/docker/using_docker_images.md#how-to-use-other-images-as-services)
 
 ### CI Examples
 
@@ -42,6 +56,7 @@
 - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
 - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
 - [Log system](logs/logs.md) Log system.
+- [Environmental Variables](administration/environmental_variables.md) to configure GitLab.
 - [Operations](operations/README.md) Keeping GitLab up and running
 - [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
 - [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
diff --git a/doc/administration/enviroment_variables.md b/doc/administration/enviroment_variables.md
new file mode 100644
index 0000000000000000000000000000000000000000..d7f5cb7c21fb51db77978aaa69b69e594af350d3
--- /dev/null
+++ b/doc/administration/enviroment_variables.md
@@ -0,0 +1,45 @@
+# Environment Variables
+
+## Introduction
+
+Commonly people configure GitLab via the gitlab.rb configuration file in the Omnibus package.
+
+But if you prefer to use environment variables we allow that too.
+
+## Supported environment variables
+
+Variable | Type | Explanation
+--- | --- | ---
+GITLAB_ROOT_PASSWORD | string | sets the password for the `root` user on installation
+GITLAB_HOST | url | hostname of the GitLab server includes http or https
+RAILS_ENV | production/development/staging/test | Rails environment
+DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5
+
+## Complete database variables
+
+As explained in the [Heroku documentation](https://devcenter.heroku.com/articles/rails-database-connection-behavior) the DATABASE_URL doesn't let you set:
+
+- adapter
+- database
+- username
+- password
+- host
+- port
+
+To do so please `cp config/database.yml.env config/database.yml` and use the following variables:
+
+Variable | Default
+--- | ---
+GITLAB_DATABASE_ADAPTER | postgresql
+GITLAB_DATABASE_ENCODING | unicode
+GITLAB_DATABASE_DATABASE | gitlab_#{ENV['RAILS_ENV']
+GITLAB_DATABASE_POOL | 10
+GITLAB_DATABASE_USERNAME | root
+GITLAB_DATABASE_PASSWORD |
+GITLAB_DATABASE_HOST | localhost
+GITLAB_DATABASE_PORT | 5432
+
+## Other variables
+
+We welcome merge requests to make more settings configurable via variables.
+Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}".
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 0b9f6406d8dd71a7c7c3b8a181666c50326117b2..808675d8605006c142a25eb188d66190c6bb73da 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -1,6 +1,6 @@
 # Groups
 
-## List project groups
+## List groups
 
 Get a list of groups. (As user: my groups, as admin: all groups)
 
@@ -21,6 +21,70 @@ GET /groups
 
 You can search for groups by name or path, see below.
 
+
+## List a group's projects
+
+Get a list of projects in this group.
+
+```
+GET /groups/:id/projects
+```
+
+Parameters:
+
+- `archived` (optional) - if passed, limit by archived status
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+- `search` (optional) - Return list of authorized projects according to a search criteria
+- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
+
+```json
+[
+  {
+    "id": 4,
+    "description": null,
+    "default_branch": "master",
+    "public": false,
+    "visibility_level": 0,
+    "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
+    "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
+    "web_url": "http://example.com/diaspora/diaspora-client",
+    "tag_list": [
+      "example",
+      "disapora client"
+    ],
+    "owner": {
+      "id": 3,
+      "name": "Diaspora",
+      "created_at": "2013-09-30T13: 46: 02Z"
+    },
+    "name": "Diaspora Client",
+    "name_with_namespace": "Diaspora / Diaspora Client",
+    "path": "diaspora-client",
+    "path_with_namespace": "diaspora/diaspora-client",
+    "issues_enabled": true,
+    "merge_requests_enabled": true,
+    "builds_enabled": true,
+    "wiki_enabled": true,
+    "snippets_enabled": false,
+    "created_at": "2013-09-30T13: 46: 02Z",
+    "last_activity_at": "2013-09-30T13: 46: 02Z",
+    "creator_id": 3,
+    "namespace": {
+      "created_at": "2013-09-30T13: 46: 02Z",
+      "description": "",
+      "id": 3,
+      "name": "Diaspora",
+      "owner_id": 1,
+      "path": "diaspora",
+      "updated_at": "2013-09-30T13: 46: 02Z"
+    },
+    "archived": false,
+    "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png"
+  }
+]
+```
+
 ## Details of a group
 
 Get all details of a group.
@@ -186,7 +250,7 @@ To get more (up to 100), pass the following as an argument to the API call:
 /groups?per_page=100
 ```
 
-And to switch pages add: 
+And to switch pages add:
 ```
 /groups?per_page=100&page=2
-```
\ No newline at end of file
+```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 0cef09d5b271fd42d2f621d9096ae3a0b394756f..8bc0a67067a1a22685a7cbf8bac444b73d0c9201 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -4,8 +4,7 @@
 
 Get all merge requests for this project. 
 The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`).
-The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests. With GitLab 8.2 the return fields `upvotes` and
-`downvotes` are deprecated and always return `0`.
+The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests.
 
 ```
 GET /projects/:id/merge_requests
@@ -58,7 +57,7 @@ Parameters:
 
 ## Get single MR
 
-Shows information about a single merge request. With GitLab 8.2 the return fields `upvotes` and `downvotes` are deprecated and always return `0`.
+Shows information about a single merge request.
 
 ```
 GET /projects/:id/merge_request/:merge_request_id
@@ -101,11 +100,46 @@ Parameters:
 }
 ```
 
+## Get single MR commits
+
+Get a list of merge request commits.
+
+```
+GET /projects/:id/merge_request/:merge_request_id/commits
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `merge_request_id` (required) - The ID of MR
+
+
+```json
+[
+  {
+    "id": "ed899a2f4b50b4370feeea94676502b42383c746",
+    "short_id": "ed899a2f4b5",
+    "title": "Replace sanitize with escape once",
+    "author_name": "Dmitriy Zaporozhets",
+    "author_email": "dzaporozhets@sphereconsultinginc.com",
+    "created_at": "2012-09-20T11:50:22+03:00",
+    "message": "Replace sanitize with escape once"
+  },
+  {
+    "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
+    "short_id": "6104942438c",
+    "title": "Sanitize for network graph",
+    "author_name": "randx",
+    "author_email": "dmitriy.zaporozhets@gmail.com",
+    "created_at": "2012-09-20T09:06:12+03:00",
+    "message": "Sanitize for network graph"
+  }
+]
+```
+
 ## Get single MR changes
 
 Shows information about the merge request including its files and changes.
-With GitLab 8.2 the return fields `upvotes` and `downvotes` are deprecated and
-always return `0`.
 
 ```
 GET /projects/:id/merge_request/:merge_request_id/changes
@@ -159,7 +193,7 @@ Parameters:
     "updated_at": "2015-02-02T19:49:26.013Z",
     "due_date": null
   },
-  "files": [
+  "changes": [
     {
     "old_path": "VERSION",
     "new_path": "VERSION",
@@ -176,9 +210,7 @@ Parameters:
 
 ## Create MR
 
-Creates a new merge request. With GitLab 8.2 the return fields `upvotes` and `
-downvotes` are deprecated and always return `0`.
-
+Creates a new merge request.
 ```
 POST /projects/:id/merge_requests
 ```
@@ -229,8 +261,7 @@ If an error occurs, an error number and a message explaining the reason is retur
 
 ## Update MR
 
-Updates an existing merge request. You can change the target branch, title, or even close the MR. With GitLab 8.2 the return fields `upvotes` and `downvotes`
-are deprecated and always return `0`.
+Updates an existing merge request. You can change the target branch, title, or even close the MR.
 
 ```
 PUT /projects/:id/merge_request/:merge_request_id
@@ -281,8 +312,7 @@ If an error occurs, an error number and a message explaining the reason is retur
 
 ## Accept MR
 
-Merge changes submitted with MR using this API. With GitLab 8.2 the return
-fields `upvotes` and `downvotes` are deprecated and always return `0`.
+Merge changes submitted with MR using this API.
 
 If merge success you get `200 OK`.
 
@@ -298,9 +328,57 @@ PUT /projects/:id/merge_request/:merge_request_id/merge
 
 Parameters:
 
-- `id` (required)                   - The ID of a project
-- `merge_request_id` (required)     - ID of MR
-- `merge_commit_message` (optional) - Custom merge commit message
+- `id` (required)                           - The ID of a project
+- `merge_request_id` (required)             - ID of MR
+- `merge_commit_message` (optional)         - Custom merge commit message
+- `should_remove_source_branch` (optional)  - if `true` removes the source branch
+- `merged_when_build_succeeds` (optional)    - if `true` the MR is merge when the build succeeds
+
+```json
+{
+  "id": 1,
+  "target_branch": "master",
+  "source_branch": "test1",
+  "project_id": 3,
+  "title": "test1",
+  "state": "merged",
+  "upvotes": 0,
+  "downvotes": 0,
+  "author": {
+    "id": 1,
+    "username": "admin",
+    "email": "admin@example.com",
+    "name": "Administrator",
+    "state": "active",
+    "created_at": "2012-04-29T08:46:00Z"
+  },
+  "assignee": {
+    "id": 1,
+    "username": "admin",
+    "email": "admin@example.com",
+    "name": "Administrator",
+    "state": "active",
+    "created_at": "2012-04-29T08:46:00Z"
+  }
+}
+```
+
+## Cancel Merge When Build Succeeds
+
+If successful you'll get `200 OK`.
+
+If you don't have permissions to accept this merge request - you'll get a 401
+
+If the merge request is already merged or closed - you get 405 and error message 'Method Not Allowed'
+
+In case the merge request is not set to be merged when the build succeeds, you'll also get a 406 error.
+```
+PUT /projects/:id/merge_request/:merge_request_id/cancel_merge_when_build_succeeds
+```
+Parameters:
+
+- `id` (required)                           - The ID of a project
+- `merge_request_id` (required)             - ID of MR
 
 ```json
 {
diff --git a/doc/api/notes.md b/doc/api/notes.md
index e7f299c099417eecb30d0434a4b7f85c19d9052e..d4d63e825abe801e09dd17e282a22f6110a41790 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -6,8 +6,7 @@ Notes are comments on snippets, issues or merge requests.
 
 ### List project issue notes
 
-Gets a list of all notes for a single issue. With GitLab 8.2 the return fields
-`upvote` and `downvote` are deprecated and always return `false`.
+Gets a list of all notes for a single issue.
 
 ```
 GET /projects/:id/issues/:issue_id/notes
@@ -35,7 +34,9 @@ Parameters:
     "created_at": "2013-10-02T09:22:45Z",
     "system": true,
     "upvote": false,
-    "downvote": false
+    "downvote": false,
+    "noteable_id": 377,
+    "noteable_type": "Issue"
   },
   {
     "id": 305,
@@ -52,7 +53,9 @@ Parameters:
     "created_at": "2013-10-02T09:56:03Z",
     "system": true,
     "upvote": false,
-    "downvote": false
+    "downvote": false,
+    "noteable_id": 121,
+    "noteable_type": "Issue"
   }
 ]
 ```
@@ -219,7 +222,12 @@ Parameters:
     "state": "active",
     "created_at": "2013-09-30T13:46:01Z"
   },
-  "created_at": "2013-10-02T08:57:14Z"
+  "created_at": "2013-10-02T08:57:14Z",
+  "system": false,
+  "upvote": false,
+  "downvote": false,
+  "noteable_id": 2,
+  "noteable_type": "MergeRequest"
 }
 ```
 
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 755cc6525c2d2fea7e84422b06c9ecfbe7b2ad85..0ca81ffd49ee1b39636a9d9045d2e6d8022523b6 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -32,7 +32,6 @@ Parameters:
 - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
 - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
 - `search` (optional) - Return list of authorized projects according to a search criteria
-- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
 
 ```json
 [
@@ -59,6 +58,7 @@ Parameters:
     "path": "diaspora-client",
     "path_with_namespace": "diaspora/diaspora-client",
     "issues_enabled": true,
+    "open_issues_count": 1,
     "merge_requests_enabled": true,
     "builds_enabled": true,
     "wiki_enabled": true,
@@ -101,6 +101,7 @@ Parameters:
     "path": "puppet",
     "path_with_namespace": "brightbox/puppet",
     "issues_enabled": true,
+    "open_issues_count": 1,
     "merge_requests_enabled": true,
     "builds_enabled": true,
     "wiki_enabled": true,
@@ -117,6 +118,16 @@ Parameters:
       "path": "brightbox",
       "updated_at": "2013-09-30T13:46:02Z"
     },
+    "permissions": {
+      "project_access": {
+        "access_level": 10,
+        "notification_level": 3
+      },
+      "group_access": {
+        "access_level": 50,
+        "notification_level": 3
+      }
+    },
     "archived": false,
     "avatar_url": null
   }
@@ -137,7 +148,21 @@ Parameters:
 - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
 - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
 - `search` (optional) - Return list of authorized projects according to a search criteria
-- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
+
+### List starred projects
+
+Get a list of projects which are starred by the authenticated user.
+
+```
+GET /projects/starred
+```
+
+Parameters:
+
+- `archived` (optional) - if passed, limit by archived status
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+- `search` (optional) - Return list of authorized projects according to a search criteria
 
 ### List ALL projects
 
@@ -153,7 +178,6 @@ Parameters:
 - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
 - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
 - `search` (optional) - Return list of authorized projects according to a search criteria
-- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
 
 ### Get single project
 
@@ -192,6 +216,7 @@ Parameters:
   "path": "diaspora-project-site",
   "path_with_namespace": "diaspora/diaspora-project-site",
   "issues_enabled": true,
+  "open_issues_count": 1,
   "merge_requests_enabled": true,
   "builds_enabled": true,
   "wiki_enabled": true,
@@ -245,9 +270,17 @@ Parameters:
     "target_id": 830,
     "target_type": "Issue",
     "author_id": 1,
-    "author_username": "john",
     "data": null,
-    "target_title": "Public project search field"
+    "target_title": "Public project search field",
+    "author": {
+      "name": "Dmitriy Zaporozhets",
+      "username": "root",
+      "id": 1,
+      "state": "active",
+      "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
+      "web_url": "http://localhost:3000/u/root"
+    },
+    "author_username": "root"
   },
   {
     "title": null,
@@ -256,6 +289,14 @@ Parameters:
     "target_id": null,
     "target_type": null,
     "author_id": 1,
+    "author": {
+      "name": "Dmitriy Zaporozhets",
+      "username": "root",
+      "id": 1,
+      "state": "active",
+      "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
+      "web_url": "http://localhost:3000/u/root"
+    },
     "author_username": "john",
     "data": {
       "before": "50d4420237a9de7be1304607147aec22e4a14af7",
@@ -292,9 +333,56 @@ Parameters:
     "target_id": 840,
     "target_type": "Issue",
     "author_id": 1,
-    "author_username": "john",
     "data": null,
-    "target_title": "Finish & merge Code search PR"
+    "target_title": "Finish & merge Code search PR",
+    "author": {
+      "name": "Dmitriy Zaporozhets",
+      "username": "root",
+      "id": 1,
+      "state": "active",
+      "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
+      "web_url": "http://localhost:3000/u/root"
+    },
+    "author_username": "root"
+  },
+  {
+    "title": null,
+    "project_id": 15,
+    "action_name": "commented on",
+    "target_id": 1312,
+    "target_type": "Note",
+    "author_id": 1,
+    "data": null,
+    "target_title": null,
+    "created_at": "2015-12-04T10:33:58.089Z",
+    "note": {
+      "id": 1312,
+      "body": "What an awesome day!",
+      "attachment": null,
+      "author": {
+        "name": "Dmitriy Zaporozhets",
+        "username": "root",
+        "id": 1,
+        "state": "active",
+        "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
+        "web_url": "http://localhost:3000/u/root"
+      },
+      "created_at": "2015-12-04T10:33:56.698Z",
+      "system": false,
+      "upvote": false,
+      "downvote": false,
+      "noteable_id": 377,
+      "noteable_type": "Issue"
+    },
+    "author": {
+      "name": "Dmitriy Zaporozhets",
+      "username": "root",
+      "id": 1,
+      "state": "active",
+      "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
+      "web_url": "http://localhost:3000/u/root"
+    },
+    "author_username": "root"
   }
 ]
 ```
@@ -335,7 +423,6 @@ Parameters:
 - `user_id` (required) - user_id of owner
 - `name` (required) - new project name
 - `description` (optional) - short project description
-- `default_branch` (optional) - 'master' by default
 - `issues_enabled` (optional)
 - `merge_requests_enabled` (optional)
 - `builds_enabled` (optional)
diff --git a/doc/api/settings.md b/doc/api/settings.md
index d1b93a09c02b40d927a8b158687f8826e677c89d..96867c679154a865f31cd2721572232eea6eb4f5 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -57,7 +57,7 @@ Parameters:
 - `default_project_visibility` - what visibility level new project receives
 - `default_snippet_visibility` - what visibility level new snippet receives
 - `restricted_signup_domains` - force people to use only corporate emails for signup
-- `user_oauth_applications` - allow users to create oauth applicaitons
+- `user_oauth_applications` - allow users to create oauth applications
 - `after_sign_out_path` - where redirect user after logout
 
 All parameters are optional. You can send only one that you want to change.
diff --git a/doc/api/users.md b/doc/api/users.md
index 7ba2db248ff0568cb2f24f524ac479beb000385d..66d2fd52526fda22713743ae6a16e0b7fdc84b49 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -90,7 +90,17 @@ GET /users
 
 You can search for users by email or username with: `/users?search=John`
 
-Also see `def search query` in `app/models/user.rb`.
+In addition, you can lookup users by username:
+
+```
+GET /users?username=:username
+```
+
+For example:
+
+```
+GET /users?username=jack_smith
+```
 
 ## Single user
 
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 97325069ceb90763c5553d6a2e8689ecef9549e8..a1f5513d88e5079e5c919ce13da55ae3ae29b0d8 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -2,16 +2,30 @@
 
 ### User documentation
 
-+ [Quick Start](quick_start/README.md)
-+ [Configuring project (.gitlab-ci.yml)](yaml/README.md)
-+ [Configuring runner](runners/README.md)
-+ [Configuring deployment](deployment/README.md)
-+ [Using Docker Images](docker/using_docker_images.md)
-+ [Using Docker Build](docker/using_docker_build.md)
-+ [Using Variables](variables/README.md)
+* [Quick Start](quick_start/README.md)
+* [Configuring project (.gitlab-ci.yml)](yaml/README.md)
+* [Configuring runner](runners/README.md)
+* [Configuring deployment](deployment/README.md)
+* [Using Docker Images](docker/using_docker_images.md)
+* [Using Docker Build](docker/using_docker_build.md)
+* [Using Variables](variables/README.md)
+* [Using SSH keys](ssh_keys/README.md)
+* [Triggering builds through the API](triggers/README.md)
+
+### Languages
+
+* [Testing PHP](languages/php.md)
+
+### Services
+
+* [Using MySQL](services/mysql.md)
+* [Using PostgreSQL](services/postgres.md)
+* [Using Redis](services/redis.md)
+* [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services)
 
 ### Examples
 
++ [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
 + [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md)
 + [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md)
 + [Test Clojure applications](examples/test-clojure-application.md)
@@ -19,5 +33,5 @@
 
 ### Administrator documentation
 
-+ [User permissions](permissions/README.md)
-+ [API](api/README.md)
+* [User permissions](permissions/README.md)
+* [API](api/README.md)
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index ef8a7ec1e8613c45d1002b68999b1b0350ae09fa..31458d61674d1c45e82907443d65638abfe23f42 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -1,19 +1,29 @@
 # Using Docker Images
-GitLab CI can use [Docker Engine](https://www.docker.com/) to build projects. 
 
-Docker is an open-source project that allows to use predefined images to run applications 
-in independent "containers" that are run within a single Linux instance. 
-[Docker Hub](https://registry.hub.docker.com/) have rich database of  built images that can be used to build applications.
+GitLab CI in conjunction with [GitLab Runner](../runners/README.md) can use
+[Docker Engine](https://www.docker.com/) to test and build any application.
 
-Docker when used with GitLab CI runs each build in separate and isolated container using predefined image and always from scratch.
-It makes it easier to have simple and reproducible build environment that can also be run on your workstation.
-This allows you to test all commands from your shell, rather than having to test them on a CI server.
+Docker is an open-source project that allows you to use predefined images to
+run applications in independent "containers" that are run within a single Linux
+instance. [Docker Hub][hub] has a rich database of pre-built images that can be
+used to test and build your applications.
 
-### Register Docker runner
-To use GitLab Runner with Docker you need to register new runner to use `docker` executor:
+Docker, when used with GitLab CI, runs each build in a separate and isolated
+container using the predefined image that is set up in
+[`.gitlab-ci.yml`](../yaml/README.md).
+
+This makes it easier to have a simple and reproducible build environment that
+can also run on your workstation. The added benefit is that you can test all
+the commands that we will explore later from your shell, rather than having to
+test them on a dedicated CI server.
+
+## Register docker runner
+
+To use GitLab Runner with docker you need to register a new runner to use the
+`docker` executor:
 
 ```bash
-gitlab-ci-multi-runner register \
+gitlab-runner register \
   --url "https://gitlab.com/" \
   --registration-token "PROJECT_REGISTRATION_TOKEN" \
   --description "docker-ruby-2.1" \
@@ -23,101 +33,79 @@ gitlab-ci-multi-runner register \
   --docker-mysql latest
 ```
 
-**The registered runner will use `ruby:2.1` image and will run two services (`postgres:latest` and `mysql:latest`) that will be accessible for time of the build.**
+The registered runner will use the `ruby:2.1` docker image and will run two
+services, `postgres:latest` and `mysql:latest`, both of which will be
+accessible during the build process.
 
-### What is image?
-The image is the name of any repository that is present in local Docker Engine or any repository that can be found at [Docker Hub](https://registry.hub.docker.com/). 
-For more information about the image and Docker Hub please read the [Docker Fundamentals](https://docs.docker.com/introduction/understanding-docker/).
+## What is image
 
-### What is service?
-Service is just another image that is run for time of your build and is linked to your build. This allows you to access the service image during build time. 
-The service image can run any application, but most common use case is to run some database container, ie.: `mysql`. 
-It's easier and faster to use existing image, run it as additional container than install `mysql` every time project is built.
+The `image` keyword is the name of the docker image that is present in the
+local Docker Engine (list all images with `docker images`) or any image that
+can be found at [Docker Hub][hub]. For more information about images and Docker
+Hub please read the [Docker Fundamentals][] documentation.
 
-#### How is service linked to the build?
-There's good document that describes how Docker linking works: [Linking containers together](https://docs.docker.com/userguide/dockerlinks/). 
-To summarize: if you add `mysql` as service to your application, the image will be used to create container that is linked to build container. 
-The service container for MySQL will be accessible under hostname `mysql`.
-So, **to access your database service you have to connect to host: `mysql` instead of socket or `localhost`**.
+In short, with `image` we refer to the docker image, which will be used to
+create a container on which your build will run.
 
-### How to use other images as services?
-You are not limited to have only database services. 
-You can hand modify `config.toml` to add any image as service found at [Docker Hub](https://registry.hub.docker.com/). 
-Look for `[runners.docker]` section:
-```
-[runners.docker]
-  image = "ruby:2.1"
-  services = ["mysql:latest", "postgres:latest"]
-```
+## What is service
 
-For example you need `wordpress` instance to test some API integration with `Wordpress`. 
-You can for example use this image: [tutum/wordpress](https://registry.hub.docker.com/u/tutum/wordpress/). 
-This is image that have fully preconfigured `wordpress` and have `MySQL` server built-in:
-```
-[runners.docker]
-  image = "ruby:2.1"
-  services = ["mysql:latest", "postgres:latest", "tutum/wordpress:latest"]
-```
+The `services` keyword defines just another docker image that is run during
+your build and is linked to the docker image that the `image` keyword defines.
+This allows you to access the service image during build time.
 
-Next time when you run your application the `tutum/wordpress` will be started 
-and you will have access to it from your build container under hostname: `tutum_wordpress`.
+The service image can run any application, but the most common use case is to
+run a database container, eg. `mysql`. It's easier and faster to use an
+existing image and run it as an additional container than install `mysql` every
+time the project is built.
 
-Alias hostname for the service is made from the image name:
-1. Everything after `:` is stripped,
-2. '/' is replaced to `_`.
+You can see some widely used services examples in the relevant documentation of
+[CI services examples](../services/README.md).
 
-### Configuring services
-Many services accept environment variables, which allow you to easily change database names or set account names depending on the environment.
+### How is service linked to the build
 
-GitLab Runner 0.5.0 and up passes all YAML-defined variables to created service containers.
+To better understand how the container linking works, read
+[Linking containers together](https://docs.docker.com/userguide/dockerlinks/).
 
-1. To configure database name for [postgres](https://registry.hub.docker.com/u/library/postgres/) service,
-you need to set POSTGRES_DB.
+To summarize, if you add `mysql` as service to your application, the image will
+then be used to create a container that is linked to the build container.
 
-    ```yaml
-    services:
-    - postgres
-    
-    variables:
-      POSTGRES_DB: gitlab
-    ```
+The service container for MySQL will be accessible under the hostname `mysql`.
+So, in order to access your database service you have to connect to the host
+named `mysql` instead of a socket or `localhost`.
 
-1. To use [mysql](https://registry.hub.docker.com/u/library/mysql/) service with empty password for time of build, 
-you need to set MYSQL_ALLOW_EMPTY_PASSWORD.
+## Overwrite image and services
 
-    ```yaml
-    services:
-    - mysql
-    
-    variables:
-      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
-    ```
+See [How to use other images as services](#how-to-use-other-images-as-services).
 
-For other possible configuration variables check the 
-https://registry.hub.docker.com/u/library/mysql/ or https://registry.hub.docker.com/u/library/postgres/
-or README page for any other Docker image.
+## How to use other images as services
 
-**Note: All variables will passed to all service containers. It's not designed to distinguish which variable should go where.**
+You are not limited to have only database services. You can add as many
+services you need to `.gitlab-ci.yml` or manually modify `config.toml`.
+Any image found at [Docker Hub][hub] can be used as a service.
 
-### Overwrite image and services
-It's possible to overwrite `docker-image` and specify services from `.gitlab-ci.yml`.
-If you add to your YAML the `image` and the `services` these parameters
-be used instead of the ones that were specified during runner's registration.
-```
+## Define image and services from `.gitlab-ci.yml`
+
+You can simply define an image that will be used for all jobs and a list of
+services that you want to use during build time.
+
+```yaml
 image: ruby:2.2
+
 services:
   - postgres:9.3
-before_install:
+
+before_script:
   - bundle install
-  
+
 test:
   script:
   - bundle exec rake spec
 ```
 
-It's possible to define image and service per-job:
-```
-before_install:
+It is also possible to define different images and services per job:
+
+```yaml
+before_script:
   - bundle install
 
 test:2.1:
@@ -135,34 +123,91 @@ test:2.2:
   - bundle exec rake spec
 ```
 
-#### How to enable overwriting?
-To enable overwriting you have to **enable it first** (it's disabled by default for security reasons). 
-You can do that by hand modifying runner configuration: `config.toml`. 
-Please go to section where is `[runners.docker]` definition for your runner. 
-Add `allowed_images` and `allowed_services` to specify what images are allowed to be picked from `.gitlab-ci.yml`:
+## Define image and services in `config.toml`
+
+Look for the `[runners.docker]` section:
+
 ```
 [runners.docker]
   image = "ruby:2.1"
-  allowed_images = ["ruby:*", "python:*"]
-  allowed_services = ["mysql:*", "redis:*"]
+  services = ["mysql:latest", "postgres:latest"]
 ```
-This enables you to use in your `.gitlab-ci.yml` any image that matches above wildcards. 
-You will be able to pick only `ruby` and `python` images. 
-The same rule can be applied to limit services. 
 
-If you are courageous enough, you can make it fully open and accept everything:
+The image and services defined this way will be added to all builds run by
+that runner.
+
+## Define an image from a private Docker registry
+
+Starting with GitLab Runner 0.6.0, you are able to define images located to
+private registries that could also require authentication.
+
+All you have to do is be explicit on the image definition in `.gitlab-ci.yml`.
+
+```yaml
+image: my.registry.tld:5000/namepace/image:tag
 ```
-[runners.docker]
-  image = "ruby:2.1"
-  allowed_images = ["*", "*/*"]
-  allowed_services = ["*", "*/*"]
+
+In the example above, GitLab Runner will look at `my.registry.tld:5000` for the
+image `namespace/image:tag`.
+
+If the repository is private you need to authenticate your GitLab Runner in the
+registry. Learn how to do that on
+[GitLab Runner's documentation][runner-priv-reg].
+
+## Accessing the services
+
+Let's say that you need a Wordpress instance to test some API integration with
+your application.
+
+You can then use for example the [tutum/wordpress][] image in your
+`.gitlab-ci.yml`:
+
+```yaml
+services:
+- tutum/wordpress:latest
 ```
 
-**It the feature is not enabled, or image isn't allowed the error message will be put into the build log.**
+When the build is run, `tutum/wordpress` will be started and you will have
+access to it from your build container under the hostname `tutum__wordpress`.
+
+The alias hostname for the service is made from the image name following these
+rules:
+
+1. Everything after `:` is stripped
+2. Backslash (`/`) is replaced with double underscores (`__`)
+
+## Configuring services
+
+Many services accept environment variables which allow you to easily change
+database names or set account names depending on the environment.
+
+GitLab Runner 0.5.0 and up passes all YAML-defined variables to the created
+service containers.
+
+For all possible configuration variables check the documentation of each image
+provided in their corresponding Docker hub page.
+
+*Note: All variables will be passed to all services containers. It's not
+designed to distinguish which variable should go where.*
+
+### PostgreSQL service example
+
+See the specific documentation for
+[using PostgreSQL as a service](../services/postgres.md).
+
+### MySQL service example
+
+See the specific documentation for
+[using MySQL as a service](../services/mysql.md).
+
+## How Docker integration works
+
+Below is a high level overview of the steps performed by docker during build
+time.
 
-### How Docker integration works
 1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`.
-1. Create cache container to store all volumes as defined in `config.toml` and `Dockerfile` of build image (`ruby:2.1` as in above example).
+1. Create cache container to store all volumes as defined in `config.toml` and
+   `Dockerfile` of build image (`ruby:2.1` as in above example).
 1. Create build container and link any service container to build container.
 1. Start build container and send build script to the container.
 1. Run build script.
@@ -171,33 +216,64 @@ If you are courageous enough, you can make it fully open and accept everything:
 1. Check exit status of build script.
 1. Remove build container and all created service containers.
 
-### How to debug a build locally
-1. Create a file with build script:
+## How to debug a build locally
+
+*Note: The following commands are run without root privileges. You should be
+able to run docker with your regular user account.*
+
+First start with creating a file named `build script`:
+
 ```bash
-$ cat <<EOF > build_script
+cat <<EOF > build_script
 git clone https://gitlab.com/gitlab-org/gitlab-ci-multi-runner.git /builds/gitlab-org/gitlab-ci-multi-runner
 cd /builds/gitlab-org/gitlab-ci-multi-runner
-make <- or any other build step
+make
 EOF
 ```
 
-1. Create service containers:
+Here we use as an example the GitLab Runner repository which contains a
+Makefile, so running `make` will execute the commands defined in the Makefile.
+Your mileage may vary, so instead of `make` you could run the command which
+is specific to your project.
+
+Then create some service containers:
+
 ```
-$ docker run -d -n service-mysql mysql:latest
-$ docker run -d -n service-postgres postgres:latest
+docker run -d -n service-mysql mysql:latest
+docker run -d -n service-postgres postgres:latest
 ```
-This will create two service containers (MySQL and PostgreSQL).
 
-1. Create a build container and execute script in its context:
+This will create two service containers, named `service-mysql` and
+`service-postgres` which use the latest MySQL and PostgreSQL images
+respectively. They will both run in the background (`-d`).
+
+Finally, create a build container by executing the `build_script` file we
+created earlier:
+
 ```
-$ cat build_script | docker run -n build -i -l mysql:service-mysql -l postgres:service-postgres ruby:2.1 /bin/bash
+docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.1 /bin/bash < build_script
 ```
-This will create build container that has two service containers linked.
-The build_script is piped using STDIN to bash interpreter which executes the build script in container. 
 
-1. At the end remove all containers:
+The above command will create a container named `build` that is spawned from
+the `ruby:2.1` image and has two services linked to it. The `build_script` is
+piped using STDIN to the bash interpreter which in turn executes the
+`build_script` in the `build` container.
+
+When you finish testing and no longer need the containers, you can remove them
+with:
+
 ```
 docker rm -f -v build service-mysql service-postgres
 ```
-This will forcefully (the `-f` switch) remove build container and service containers 
-and all volumes (the `-v` switch) that were created with the container creation.
+
+This will forcefully (`-f`) remove the `build` container, the two service
+containers as well as all volumes (`-v`) that were created with the container
+creation.
+
+[Docker Fundamentals]: https://docs.docker.com/engine/introduction/understanding-docker/
+[hub]: https://hub.docker.com/
+[linking-containers]: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/
+[tutum/wordpress]: https://registry.hub.docker.com/u/tutum/wordpress/
+[postgres-hub]: https://registry.hub.docker.com/u/library/postgres/
+[mysql-hub]: https://registry.hub.docker.com/u/library/mysql/
+[runner-priv-reg]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/configuration/advanced-configuration.md#using-a-private-docker-registry
diff --git a/doc/ci/img/builds_tab.png b/doc/ci/img/builds_tab.png
new file mode 100644
index 0000000000000000000000000000000000000000..d088b8b329dc5dca75513e4fbe27d2323203d770
Binary files /dev/null and b/doc/ci/img/builds_tab.png differ
diff --git a/doc/ci/languages/README.md b/doc/ci/languages/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..54b2343e08b4a91b6465451e7ad4511bf9ef2164
--- /dev/null
+++ b/doc/ci/languages/README.md
@@ -0,0 +1,7 @@
+### Languages
+
+This is a list of languages you can test with GitLab CI. Each section has
+comprehensive documentation and comes with a test repository hosted on
+GitLab.com
+
++ [Testing PHP](php.md)
diff --git a/doc/ci/languages/php.md b/doc/ci/languages/php.md
new file mode 100644
index 0000000000000000000000000000000000000000..dacb67fa3ff4cfaf209fe96c0161b13d13730a91
--- /dev/null
+++ b/doc/ci/languages/php.md
@@ -0,0 +1,284 @@
+# Testing PHP projects
+
+This guide covers basic building instructions for PHP projects.
+
+There are covered two cases: testing using the Docker executor and testing
+using the Shell executor.
+
+## Test PHP projects using the Docker executor
+
+While it is possible to test PHP apps on any system, this would require manual
+configuration from the developer. To overcome this we will be using the
+official [PHP docker image][php-hub] that can be found in Docker Hub.
+
+This will allow us to test PHP projects against different versions of PHP.
+However, not everything is plug 'n' play, you still need to onfigure some
+things manually.
+
+As with every build, you need to create a valid `.gitlab-ci.yml` describing the
+build environment.
+
+Let's first specify the PHP image that will be used for the build process
+(you can read more about what an image means in the Runner's lingo reading
+about [Using Docker images](../docker/using_docker_images.md#what-is-image)).
+
+Start by adding the image to your `.gitlab-ci.yml`:
+
+```yaml
+image: php:5.6
+```
+
+The official images are great, but they lack a few useful tools for testing.
+We need to first prepare the build environment. A way to overcome this is to
+create a script which installs all prerequisites prior the actual testing is
+done.
+
+Let's create a `ci/docker_install.sh` file in the root directory of our
+repository with the following content:
+
+```bash
+#!/bin/bash
+
+# We need to install dependencies only for Docker
+[[ ! -e /.dockerinit ]] && exit 0
+
+set -xe
+
+# Install git (the php image doesn't have it) which is required by composer
+apt-get update -yqq
+apt-get install git -yqq
+
+# Install phpunit, the tool that we will use for testing
+curl -o /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar
+chmod +x /usr/local/bin/phpunit
+
+# Install mysql driver
+# Here you can install any other extension that you need
+docker-php-ext-install pdo_mysql
+```
+
+You might wonder what `docker-php-ext-install` is. In short, it is a script
+provided by the official php docker image that you can use to easilly install
+extensions. For more information read the the documentation at
+<https://hub.docker.com/_/php/>.
+
+Now that we created the script that contains all prerequisites for our build
+environment, let's add it in `.gitlab-ci.yml`:
+
+```yaml
+...
+
+before_script:
+- bash ci/docker_install.sh > /dev/null
+
+...
+```
+
+Last step, run the actual tests using `phpunit`:
+
+```yaml
+...
+
+test:app:
+  script:
+  - phpunit --configuration phpunit_myapp.xml
+
+...
+```
+
+Finally, commit your files and push them to GitLab to see your build succeeding
+(or failing).
+
+The final `.gitlab-ci.yml` should look similar to this:
+
+```yaml
+# Select image from https://hub.docker.com/_/php/
+image: php:5.6
+
+before_script:
+# Install dependencies
+- ci/docker_install.sh > /dev/null
+
+test:app:
+  script:
+  - phpunit --configuration phpunit_myapp.xml
+```
+
+### Test against different PHP versions in Docker builds
+
+Testing against multiple versions of PHP is super easy. Just add another job
+with a different docker image version and the runner will do the rest:
+
+```yaml
+before_script:
+# Install dependencies
+- ci/docker_install.sh > /dev/null
+
+# We test PHP5.6
+test:5.6:
+  image: php:5.6
+  script:
+  - phpunit --configuration phpunit_myapp.xml
+
+# We test PHP7.0 (good luck with that)
+test:7.0:
+  image: php:7.0
+  script:
+  - phpunit --configuration phpunit_myapp.xml
+```
+
+### Custom PHP configuration in Docker builds
+
+There are times where you will need to customise your PHP environment by
+putting your `.ini` file into `/usr/local/etc/php/conf.d/`. For that purpose
+add a `before_script` action:
+
+```yaml
+before_script:
+- cp my_php.ini /usr/local/etc/php/conf.d/test.ini
+```
+
+Of course, `my_php.ini` must be present in the root directory of your repository.
+
+## Test PHP projects using the Shell executor
+
+The shell executor runs your builds in a terminal session on your server.
+Thus, in order to test your projects you first need to make sure that all
+dependencies are installed.
+
+For example, in a VM running Debian 8 we first update the cache, then we
+install `phpunit` and `php5-mysql`:
+
+```bash
+sudo apt-get update -y
+sudo apt-get install -y phpunit php5-mysql
+```
+
+Next, add the following snippet to your `.gitlab-ci.yml`:
+
+```yaml
+test:app:
+  script:
+  - phpunit --configuration phpunit_myapp.xml
+```
+
+Finally, push to GitLab and let the tests begin!
+
+### Test against different PHP versions in Shell builds
+
+The [phpenv][] project allows you to easily manage different versions of PHP
+each with its own config. This is specially usefull when testing PHP projects
+with the Shell executor.
+
+You will have to install it on your build machine under the `gitlab-runner`
+user following [the upstream installation guide][phpenv-installation].
+
+Using phpenv also allows to easily configure the PHP environment with:
+
+```
+phpenv config-add my_config.ini
+```
+
+*__Important note:__ It seems `phpenv/phpenv`
+ [is abandoned](https://github.com/phpenv/phpenv/issues/57). There is a fork
+ at [madumlao/phpenv](https://github.com/madumlao/phpenv) that tries to bring
+ the project back to life. [CHH/phpenv](https://github.com/CHH/phpenv) also
+ seems like a good alternative. Picking any of the mentioned tools will work
+ with the basic phpenv commands. Guiding you to choose the right phpenv is out
+ of the scope of this tutorial.*
+
+### Install custom extensions
+
+Since this is a pretty bare installation of the PHP environment, you may need
+some extensions that are not currently present on the build machine.
+
+To install additional extensions simply execute:
+
+```bash
+pecl install <extension>
+```
+
+It's not advised to add this to `.gitlab-ci.yml`. You should execute this
+command once, only to setup the build environment.
+
+## Extend your tests
+
+### Using atoum
+
+Instead of PHPUnit, you can use any other tool to run unit tests. For example
+you can use [atoum](https://github.com/atoum/atoum):
+
+```yaml
+before_script:
+- wget http://downloads.atoum.org/nightly/mageekguy.atoum.phar
+
+test:atoum:
+  script:
+  - php mageekguy.atoum.phar
+```
+
+### Using Composer
+
+The majority of the PHP projects use Composer for managing their PHP packages.
+In order to execute Composer before running your tests, simply add the
+following in your `.gitlab-ci.yml`:
+
+```yaml
+...
+
+# Composer stores all downloaded packages in the vendor/ directory.
+# Do not use the following if the vendor/ directory is commited to
+# your git repository.
+cache:
+  paths:
+  - vendor/
+
+before_script:
+# Install composer dependencies
+- curl -sS https://getcomposer.org/installer | php
+- php composer.phar install
+
+...
+```
+
+## Access private packages / dependencies
+
+If your test suite needs to access a private repository, you need to configure
+[the SSH keys](../ssh_keys/README.md) in order to be able to clone it.
+
+## Use databases or other services
+
+Most of the time you will need a running database in order for your tests to
+run. If you are using the Docker executor you can leverage Docker's ability to
+link to other containers. In GitLab Runner lingo, this can be achieved by
+defining a `service`.
+
+This functionality is covered in [the CI services](../services/README.md)
+documentation.
+
+## Testing things locally
+
+With GitLab Runner 1.0 you can also test any changes locally. From your
+terminal execute:
+
+```bash
+# Check using docker executor
+gitlab-runner exec docker test:app
+
+# Check using shell executor
+gitlab-runner exec shell test:app
+```
+
+## Example project
+
+We have set up an [Example PHP Project][php-example-repo] for your convenience
+that runs on [GitLab.com](https://gitlab.com) using our publicly available
+[shared runners](../runners/README.md).
+
+Want to hack on it? Simply fork it, commit and push  your changes. Within a few
+moments the changes will be picked by a public runner and the build will begin.
+
+[php-hub]: https://hub.docker.com/_/php/
+[phpenv]: https://github.com/phpenv/phpenv
+[phpenv-installation]: https://github.com/phpenv/phpenv#installation
+[php-example-repo]: https://gitlab.com/gitlab-examples/php
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index d69064a91fd0cf630533a95974790f170c9cc001..a9b36139de94c376fca4576388d59eb6c6e2d9b3 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -1,44 +1,62 @@
 # Quick Start
 
-To start building projects with GitLab CI a few steps needs to be done.
+Starting from version 8.0, GitLab Continuous Integration (CI) is fully
+integrated into GitLab itself and is enabled by default on all projects.
 
-## 1. Install GitLab and CI
+This guide assumes that you:
 
-First you need to have a working GitLab and GitLab CI instance.
+- have a working GitLab instance of version 8.0 or higher or are using
+  [GitLab.com](https://gitlab.com/users/sign_in)
+- have a project in GitLab that you would like to use CI for
 
-You can omit this step if you use [GitLab.com](https://GitLab.com/).
+In brief, the steps needed to have a working CI can be summed up to:
 
-## 2. Create repository on GitLab
+1. Create a new project
+1. Add `.gitlab-ci.yml` to the git repository and push to GitLab
+1. Configure a Runner
 
-Once you login on your GitLab add a new repository where you will store your source code.
-Push your application to that repository.
+From there on, on every push to your git repository the build will be
+automagically started by the Runner and will appear under the project's
+`/builds` page.
 
-## 3. Add project to CI
+Now, let's break it down to pieces and work on solving the GitLab CI puzzle.
 
-The next part is to login to GitLab CI.
-Point your browser to the URL you have set GitLab or use [gitlab.com/ci](https://gitlab.com/ci/).
+## Creating a `.gitlab-ci.yml` file
 
-On the first screen you will see a list of GitLab's projects that you have access to:
+Before you create `.gitlab-ci.yml` let's first explain in brief what this is
+all about.
 
-![Projects](projects.png)
+### What is `.gitlab-ci.yml`
 
-Click **Add Project to CI**.
-This will create project in CI and authorize GitLab CI to fetch sources from GitLab.
+The `.gitlab-ci.yml` file is where you configure what CI does with your project.
+It lives in the root of your repository.
 
-> GitLab CI creates unique token that is used to configure GitLab CI service in GitLab.
-> This token allows to access GitLab's repository and configures GitLab to trigger GitLab CI webhook on **Push events** and **Tag push events**.
-> You can see that token by going to Project's Settings > Services > GitLab CI.
-> You will see there token, the same token is assigned in GitLab CI settings of project.
+On any push to your repository, GitLab will look for the `.gitlab-ci.yml`
+file and start builds on _Runners_ according to the contents of the file,
+for that commit.
 
-## 4. Create project's configuration - .gitlab-ci.yml
+Because `.gitlab-ci.yml` is in the repository, it is version controlled,
+old versions still build succesfully, forks can easily make use of CI,
+branches can have separate builds and you have a single source of truth for CI.
+You can read more about the reasons why we are using `.gitlab-ci.yml`
+[in our blog about it][blog-ci].
 
-The next: You have to define how your project will be built.
-GitLab CI uses [YAML](https://en.wikipedia.org/wiki/YAML) file to store build configuration.
-You need to create `.gitlab-ci.yml` in root directory of your repository:
+**Note:** `.gitlab-ci.yml` is a [YAML](https://en.wikipedia.org/wiki/YAML) file
+so you have to pay extra attention to the identation. Always use spaces, not
+tabs.
+
+### Creating a simple `.gitlab-ci.yml` file
+
+You need to create a file named `.gitlab-ci.yml` in the root directory of your
+repository. Below is an example for a Ruby on Rails project.
 
 ```yaml
 before_script:
-  - bundle install
+  - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+  - ruby -v
+  - which ruby
+  - gem install bundler --no-ri --no-rdoc
+  - bundle install --jobs $(nproc)  "${FLAGS[@]}"
 
 rspec:
   script:
@@ -49,71 +67,131 @@ rubocop:
     - bundle exec rubocop
 ```
 
-This is the simplest possible build configuration that will work for most Ruby applications:
-1. Define two jobs `rspec` and `rubocop` with two different commands to be executed.
-1. Before every job execute commands defined by `before_script`.
+This is the simplest possible build configuration that will work for most Ruby
+applications:
+
+1. Define two jobs `rspec` and `rubocop` (the names are arbitrary) with
+   different commands to be executed.
+1. Before every job, the commands defined by `before_script` are executed.
 
-The `.gitlab-ci.yml` defines set of jobs with constrains how and when they should be run.
-The jobs are defined as top-level elements with name and always have to contain the `script`.
-Jobs are used to create builds, which are then picked by [runners](../runners/README.md) and executed within environment of the runner.
-What is important that each job is run independently from each other. 
+The `.gitlab-ci.yml` file defines sets of jobs with constraints of how and when
+they should be run. The jobs are defined as top-level elements with a name (in
+our case `rspec` and `rubocop`) and always have to contain the `script` keyword.
+Jobs are used to create builds, which are then picked by
+[Runners](../runners/README.md) and executed within the environment of the Runner.
 
-For more information and complete `.gitlab-ci.yml` syntax, please check the [Configuring project (.gitlab-ci.yml)](../yaml/README.md).
+What is important is that each job is run independently from each other.
 
-## 5. Add file and push .gitlab-ci.yml to repository
+If you want to check whether your `.gitlab-ci.yml` file is valid, there is a
+Lint tool under the page `/ci/lint` of your GitLab instance. You can also find
+the link under **Settings > CI settings** in your project.
 
-Once you created `.gitlab-ci.yml` you should add it to git repository and push it to GitLab.
+For more information and a complete `.gitlab-ci.yml` syntax, please check
+[the documentation on .gitlab-ci.yml](../yaml/README.md).
+
+### Push `.gitlab-ci.yml` to GitLab
+
+Once you've created `.gitlab-ci.yml`, you should add it to your git repository
+and push it to GitLab.
 
 ```bash
 git add .gitlab-ci.yml
-git commit
+git commit -m "Add .gitlab-ci.yml"
 git push origin master
 ```
 
-If you refresh the project's page on GitLab CI you will notice a one new commit:
+Now if you go to the **Builds** page you will see that the builds are pending.
+
+You can also go to the **Commits** page and notice the little clock icon next
+to the commit SHA.
+
+![New commit pending](img/new_commit.png)
+
+Clicking on the clock icon you will be directed to the builds page for that
+specific commit.
+
+![Single commit builds page](img/single_commit_status_pending.png)
+
+Notice that there are two jobs pending which are named after what we wrote in
+`.gitlab-ci.yml`. The red triangle indicates that there is no Runner configured
+yet for these builds.
+
+The next step is to configure a Runner so that it picks the pending jobs.
+
+## Configuring a Runner
+
+In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`.
+A Runner can be a virtual machine, a VPS, a bare-metal machine, a docker
+container or even a cluster of containers. GitLab and the Runners communicate
+through an API, so the only needed requirement is that the machine on which the
+Runner is configured to has Internet access.
+
+A Runner can be specific to a certain project or serve multiple projects in
+GitLab. If it serves all projects it's called a _Shared Runner_.
+
+Find more information about different Runners in the
+[Runners](../runners/README.md) documentation.
+
+You can find whether any Runners are assigned to your project by going to
+**Settings > Runners**. Setting up a Runner is easy and straightforward. The
+official Runner supported by GitLab is written in Go and can be found at
+<https://gitlab.com/gitlab-org/gitlab-ci-multi-runner>.
+
+In order to have a functional Runner you need to follow two steps:
+
+1. [Install it][runner-install]
+2. [Configure it](../runners/README.md#registering-a-specific-runner)
+
+Follow the links above to set up your own Runner or use a Shared Runner as
+described in the next section.
+
+For other types of unofficial Runners written in other languages, see the
+[instructions for the various GitLab Runners](https://about.gitlab.com/gitlab-ci/#gitlab-runner).
+
+Once the Runner has been set up, you should see it on the Runners page of your
+project, following **Settings > Runners**.
 
-![](new_commit.png)
+![Activated runners](img/runners_activated.png)
 
-However the commit has status **pending** which means that commit was not yet picked by runner.
+### Shared Runners
 
-## 6. Configure runner
+If you use [GitLab.com](https://gitlab.com/) you can use **Shared Runners**
+provided by GitLab Inc.
 
-In GitLab CI, Runners run your builds.
-A runner is a machine (can be virtual, bare-metal or VPS) that picks up builds through the coordinator API of GitLab CI.
+These are special virtual machines that run on GitLab's infrastructure and can
+build any project.
 
-A runner can be specific to a certain project or serve any project in GitLab CI.
-A runner that serves all projects is called a shared runner.
-More information about different runner types can be found in [Configuring runner](../runners/README.md).
+To enable **Shared Runners** you have to go to your project's
+**Settings > Runners** and click **Enable shared runners**.
 
-To check if you have runners assigned to your project go to **Runners**. You will find there information how to setup project specific runner:
+[Read more on Shared Runners](../runners/README.md).
 
-1. Install GitLab Runner software. Checkout the [GitLab Runner](https://about.gitlab.com/gitlab-ci/#gitlab-runner) section to install it.
-1. Specify following URL during runner setup: https://gitlab.com/ci/
-1. Use the following registration token during setup: TOKEN
+## Seeing the status of your build
 
-If you do it correctly your runner should be shown under **Runners activated for this project**:
+After configuring the Runner succesfully, you should see the status of your
+last commit change from _pending_ to either _running_, _success_ or _failed_.
 
-![](runners_activated.png)
+You can view all builds, by going to the **Builds** page in your project.
 
-### Shared runners
+![Commit status](img/builds_status.png)
 
-If you use [gitlab.com/ci](https://gitlab.com/ci/) you can use **Shared runners** provided by GitLab Inc.
-These are special virtual machines that are run on GitLab's infrastructure that can build any project.
-To enable **Shared runners** you have to go to **Runners** and click **Enable shared runners** for this project.
+By clicking on a Build ID, you will be able to see the log of that build.
+This is important to diagnose why a build failed or acted differently than
+you expected.
 
-## 7. Check status of commit
+![Build log](img/build_log.png)
 
-If everything went OK and you go to commit, the status of the commit should change from **pending** to either **running**, **success** or **failed**.
+You are also able to view the status of any commit in the various pages in
+GitLab, such as **Commits** and **Merge Requests**.
 
-![](commit_status.png)
+## Next steps
 
-You can click **Build ID** to view build log for specific job.
+Awesome! You started using CI in GitLab!
 
-## 8. Congratulations!
+Next you can look into doing more with the CI. Many people are using GitLab
+to package, containerize, test and deploy software.
 
-You managed to build your first project using GitLab CI.
-You may need to tune your `.gitlab-ci.yml` file to implement build plan for your project.
-A few examples how it can be done you can find on [Examples](../examples/README.md) page.
+Visit our various languages examples at <https://gitlab.com/groups/gitlab-examples>.
 
-GitLab CI also offers **the Lint** tool to verify validity of your `.gitlab-ci.yml` which can be useful to troubleshoot potential problems.
-The Lint is available from project's settings or by adding `/lint` to GitLab CI url.
+[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation
+[blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
diff --git a/doc/ci/quick_start/build_status.png b/doc/ci/quick_start/build_status.png
deleted file mode 100644
index 333259e6acd301b579ebb7b7682b1df1e635a61a..0000000000000000000000000000000000000000
Binary files a/doc/ci/quick_start/build_status.png and /dev/null differ
diff --git a/doc/ci/quick_start/commit_status.png b/doc/ci/quick_start/commit_status.png
deleted file mode 100644
index 725b79e6f91d1de8e8240c28f911b2753d92222a..0000000000000000000000000000000000000000
Binary files a/doc/ci/quick_start/commit_status.png and /dev/null differ
diff --git a/doc/ci/quick_start/img/build_log.png b/doc/ci/quick_start/img/build_log.png
new file mode 100644
index 0000000000000000000000000000000000000000..89e6cd40cb61d890b7ee1ce0137c738a18526e74
Binary files /dev/null and b/doc/ci/quick_start/img/build_log.png differ
diff --git a/doc/ci/quick_start/img/builds_status.png b/doc/ci/quick_start/img/builds_status.png
new file mode 100644
index 0000000000000000000000000000000000000000..b8e6c2a361a093059f2ab189a2e281a22175ad7a
Binary files /dev/null and b/doc/ci/quick_start/img/builds_status.png differ
diff --git a/doc/ci/quick_start/img/new_commit.png b/doc/ci/quick_start/img/new_commit.png
new file mode 100644
index 0000000000000000000000000000000000000000..3d3c9d5c0bd078333a7bc1e670b9a7dc736a2fcc
Binary files /dev/null and b/doc/ci/quick_start/img/new_commit.png differ
diff --git a/doc/ci/quick_start/img/runners_activated.png b/doc/ci/quick_start/img/runners_activated.png
new file mode 100644
index 0000000000000000000000000000000000000000..eafcfd6ecd5daa3ebdea96496b2126f756ace99e
Binary files /dev/null and b/doc/ci/quick_start/img/runners_activated.png differ
diff --git a/doc/ci/quick_start/img/single_commit_status_pending.png b/doc/ci/quick_start/img/single_commit_status_pending.png
new file mode 100644
index 0000000000000000000000000000000000000000..23b3bb5acfcfd4d44f76152f92fab81d95d7b500
Binary files /dev/null and b/doc/ci/quick_start/img/single_commit_status_pending.png differ
diff --git a/doc/ci/quick_start/img/status_pending.png b/doc/ci/quick_start/img/status_pending.png
new file mode 100644
index 0000000000000000000000000000000000000000..a049ec2a5ba560c222078da1c45b3e358ce15dd3
Binary files /dev/null and b/doc/ci/quick_start/img/status_pending.png differ
diff --git a/doc/ci/quick_start/new_commit.png b/doc/ci/quick_start/new_commit.png
deleted file mode 100644
index 3839e893c177ee81865b6791d2b81b664a4dcfc0..0000000000000000000000000000000000000000
Binary files a/doc/ci/quick_start/new_commit.png and /dev/null differ
diff --git a/doc/ci/quick_start/projects.png b/doc/ci/quick_start/projects.png
deleted file mode 100644
index 0b3430a69dbfafbe71d780c13025226499e82407..0000000000000000000000000000000000000000
Binary files a/doc/ci/quick_start/projects.png and /dev/null differ
diff --git a/doc/ci/quick_start/runners.png b/doc/ci/quick_start/runners.png
deleted file mode 100644
index 25b4046bc00a8843d2dc68125ce8a9864e541df3..0000000000000000000000000000000000000000
Binary files a/doc/ci/quick_start/runners.png and /dev/null differ
diff --git a/doc/ci/quick_start/runners_activated.png b/doc/ci/quick_start/runners_activated.png
deleted file mode 100644
index c934bd12f41dc17d10eb131b233f630b18fc1761..0000000000000000000000000000000000000000
Binary files a/doc/ci/quick_start/runners_activated.png and /dev/null differ
diff --git a/doc/ci/services/README.md b/doc/ci/services/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..1ebb0a4a25085cef1fb65afb87a19d7a379daa27
--- /dev/null
+++ b/doc/ci/services/README.md
@@ -0,0 +1,9 @@
+## GitLab CI Services
+
+GitLab CI uses the `services` keyword to define what docker containers should be
+linked with your base image. Below is a list of examples you may use.
+
++ [Using MySQL](mysql.md)
++ [Using PostgreSQL](postgres.md)
++ [Using Redis](redis.md)
++ [Using Other Services](../docker/using_docker_images.md#how-to-use-other-images-as-services)
diff --git a/doc/ci/services/docker-services.md b/doc/ci/services/docker-services.md
new file mode 100644
index 0000000000000000000000000000000000000000..df36ebaf7d41f84ceb5c4ec451e5e9964d128fe9
--- /dev/null
+++ b/doc/ci/services/docker-services.md
@@ -0,0 +1,5 @@
+## GitLab CI Services
+
++ [Using MySQL](mysql.md)
++ [Using PostgreSQL](postgres.md)
++ [Using Redis](redis.md)
diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md
new file mode 100644
index 0000000000000000000000000000000000000000..c66d77122b2321a99e22d9b4f93171830784cca6
--- /dev/null
+++ b/doc/ci/services/mysql.md
@@ -0,0 +1,118 @@
+# Using MySQL
+
+As many applications depend on MySQL as their database, you will eventually
+need it in order for your tests to run. Below you are guided how to do this
+with the Docker and Shell executors of GitLab Runner.
+
+## Use MySQL with the Docker executor
+
+If you are using [GitLab Runner](../runners/README.md) with the Docker executor
+you basically have everything set up already.
+
+First, in your `.gitlab-ci.yml` add:
+
+```yaml
+services:
+  - mysql:latest
+
+variables:
+  # Configure mysql environment variables (https://hub.docker.com/_/mysql/)
+  MYSQL_DATABASE: el_duderino
+  MYSQL_ROOT_PASSWORD: mysql_strong_password
+```
+
+And then configure your application to use the database, for example:
+
+```yaml
+Host: mysql
+User: root
+Password: mysql_strong_password
+Database: el_duderino
+```
+
+If you are wondering why we used `mysql` for the `Host`, read more at
+[How is service linked to the build](../docker/using_docker_images.md#how-is-service-linked-to-the-build).
+
+You can also use any other docker image available on [Docker Hub][hub-mysql].
+For example, to use MySQL 5.5 the service becomes `mysql:5.5`.
+
+The `mysql` image can accept some environment variables. For more details
+check the documentation on [Docker Hub][hub-mysql].
+
+## Use MySQL with the Shell executor
+
+You can also use MySQL on manually configured servers that are using
+GitLab Runner with the Shell executor.
+
+First install the MySQL server:
+
+```bash
+sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
+```
+
+Pick a MySQL root password (can be anything), and type it twice when asked.
+
+*Note: As a security measure you can run `mysql_secure_installation` to
+remove anonymous users, drop the test database and disable remote logins with
+the root user.*
+
+The next step is to create a user, so login to MySQL as root:
+
+```bash
+mysql -u root -p
+```
+
+Then create a user (in our case `runner`) which will be used by your
+application. Change `$password` in the command below to a real strong password.
+
+*Note: Do not type `mysql>`, this is part of the MySQL prompt.*
+
+```bash
+mysql> CREATE USER 'runner'@'localhost' IDENTIFIED BY '$password';
+```
+
+Create the database:
+
+```bash
+mysql> CREATE DATABASE IF NOT EXISTS `el_duderino` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
+```
+
+Grant the necessary permissions on the database:
+
+```bash
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES ON `el_duderino`.* TO 'runner'@'localhost';
+```
+
+If all went well you can now quit the database session:
+
+```bash
+mysql> \q
+```
+
+Now, try to connect to the newly created database to check that everything is
+in place:
+
+```bash
+mysql -u runner -p -D el_duderino
+```
+
+As a final step, configure your application to use the database, for example:
+
+```bash
+Host: localhost
+User: runner
+Password: $password
+Database: el_duderino
+```
+
+## Example project
+
+We have set up an [Example MySQL Project][mysql-example-repo] for your
+convenience that runs on [GitLab.com](https://gitlab.com) using our publicly
+available [shared runners](../runners/README.md).
+
+Want to hack on it? Simply fork it, commit and push  your changes. Within a few
+moments the changes will be picked by a public runner and the build will begin.
+
+[hub-mysql]: https://hub.docker.com/_/mysql/
+[mysql-example-repo]: https://gitlab.com/gitlab-examples/mysql
diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md
new file mode 100644
index 0000000000000000000000000000000000000000..17d21dbda1cd5c0d58cef5a19abf33d611fe7855
--- /dev/null
+++ b/doc/ci/services/postgres.md
@@ -0,0 +1,114 @@
+# Using PostgreSQL
+
+As many applications depend on PostgreSQL as their database, you will
+eventually need it in order for your tests to run. Below you are guided how to
+do this with the Docker and Shell executors of GitLab Runner.
+
+## Use PostgreSQL with the Docker executor
+
+If you are using [GitLab Runner](../runners/README.md) with the Docker executor
+you basically have everything set up already.
+
+First, in your `.gitlab-ci.yml` add:
+
+```yaml
+services:
+  - postgres:latest
+
+variables:
+  POSTGRES_DB: nice_marmot
+  POSTGRES_USER: runner
+  POSTGRES_PASSWORD: ""
+```
+
+And then configure your application to use the database, for example:
+
+```yaml
+Host: postgres
+User: runner
+Password:
+Database: nice_marmot
+```
+
+If you are wondering why we used `postgres` for the `Host`, read more at
+[How is service linked to the build](../docker/using_docker_images.md#how-is-service-linked-to-the-build).
+
+You can also use any other docker image available on [Docker Hub][hub-pg].
+For example, to use PostgreSQL 9.3 the service becomes `postgres:9.3`.
+
+The `postgres` image can accept some environment variables. For more details
+check the documentation on [Docker Hub][hub-pg].
+
+## Use PostgreSQL with the Shell executor
+
+You can also use PostgreSQL on manually configured servers that are using
+GitLab Runner with the Shell executor.
+
+First install the PostgreSQL server:
+
+```bash
+sudo apt-get install -y postgresql postgresql-client libpq-dev
+```
+
+The next step is to create a user, so login to PostgreSQL:
+
+```bash
+sudo -u postgres psql -d template1
+```
+
+Then create a user (in our case `runner`) which will be used by your
+application. Change `$password` in the command below to a real strong password.
+
+*__Note:__ Do not type `template1=#`, this is part of the PostgreSQL prompt.*
+
+```bash
+template1=# CREATE USER runner WITH PASSWORD '$password' CREATEDB;
+```
+
+*__Note:__ Notice that we created the user with the privilege to be able to
+create databases (`CREATEDB`). In the following steps we will create a database 
+explicitly for that user but having that privilege can be useful if in your
+testing framework you have tools that drop and create databases.*
+
+Create the database and grant all privileges on it for the user `runner`:
+
+```bash
+template1=# CREATE DATABASE nice_marmot OWNER runner;
+```
+
+If all went well you can now quit the database session:
+
+```bash
+template1=# \q
+```
+
+Now, try to connect to the newly created database with the user `runner` to
+check that everything is in place.
+
+```bash
+psql -U runner -h localhost -d nice_marmot -W
+```
+
+*__Note:__ We are explicitly telling `psql` to connect to localhost in order
+to use the md5 authentication. If you omit this step you will be denied access.*
+
+Finally, configure your application to use the database, for example:
+
+```yaml
+Host: localhost
+User: runner
+Password: $password
+Database: nice_marmot
+```
+
+## Example project
+
+We have set up an [Example PostgreSQL Project][postgres-example-repo] for your
+convenience that runs on [GitLab.com](https://gitlab.com) using our publicly
+available [shared runners](../runners/README.md).
+
+Want to hack on it? Simply fork it, commit and push  your changes. Within a few
+moments the changes will be picked by a public runner and the build will begin.
+
+[hub-pg]: https://hub.docker.com/_/postgres/
+[postgres-example-repo]: https://gitlab.com/gitlab-examples/postgres
diff --git a/doc/ci/services/redis.md b/doc/ci/services/redis.md
new file mode 100644
index 0000000000000000000000000000000000000000..b281e8f9f604e029c53766a880057f69a6dcbe58
--- /dev/null
+++ b/doc/ci/services/redis.md
@@ -0,0 +1,69 @@
+# Using Redis
+
+As many applications depend on Redis as their key-value store, you will
+eventually need it in order for your tests to run. Below you are guided how to
+do this with the Docker and Shell executors of GitLab Runner.
+
+## Use Redis with the Docker executor
+
+If you are using [GitLab Runner](../runners/README.md) with the Docker executor
+you basically have everything set up already.
+
+First, in your `.gitlab-ci.yml` add:
+
+```yaml
+services:
+  - redis:latest
+```
+
+Then you need to configure your application to use the Redis database, for
+example:
+
+```yaml
+Host: redis
+```
+
+And that's it. Redis will now be available to be used within your testing
+framework.
+
+You can also use any other docker image available on [Docker Hub][hub-redis].
+For example, to use Redis 2.8 the service becomes `redis:2.8`.
+
+## Use Redis with the Shell executor
+
+Redis can also be used on manually configured servers that are using GitLab
+Runner with the Shell executor.
+
+In your build machine install the Redis server:
+
+```bash
+sudo apt-get install redis-server
+```
+
+Verify that you can connect to the server with the `gitlab-runner` user:
+
+```bash
+# Try connecting the the Redis server
+sudo -u gitlab-runner -H redis-cli
+
+# Quit the session
+127.0.0.1:6379> quit
+```
+
+Finally, configure your application to use the database, for example:
+
+```yaml
+Host: localhost
+```
+
+## Example project
+
+We have set up an [Example Redis Project][redis-example-repo] for your convenience
+that runs on [GitLab.com](https://gitlab.com) using our publicly available
+[shared runners](../runners/README.md).
+
+Want to hack on it? Simply fork it, commit and push  your changes. Within a few
+moments the changes will be picked by a public runner and the build will begin.
+
+[hub-redis]: https://hub.docker.com/_/redis/
+[redis-example-repo]: https://gitlab.com/gitlab-examples/redis
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..210f9c3e849a199da62f2af285162448c5adfec6
--- /dev/null
+++ b/doc/ci/ssh_keys/README.md
@@ -0,0 +1,109 @@
+# Using SSH keys
+
+GitLab currently doesn't have built-in support for managing SSH keys in a build
+environment.
+
+The SSH keys can be useful when:
+
+1. You want to checkout internal submodules
+2. You want to download private packages using your package manager (eg. bundler)
+3. You want to deploy your application to eg. Heroku or your own server
+4. You want to execute SSH commands from the build server to the remote server
+5. You want to rsync files from your build server to the remote server
+
+If anything of the above rings a bell, then you most likely need an SSH key.
+
+## Inject keys in your build server
+
+The most widely supported method is to inject an SSH key into your build
+environment by extending your `.gitlab-ci.yml`.
+
+This is the universal solution which works with any type of executor
+(docker, shell, etc.).
+
+### How it works
+
+1. Create a new SSH key pair with [ssh-keygen][]
+2. Add the private key as a **Secret Variable** to the project
+3. Run the [ssh-agent][] during build to load the private key.
+
+## SSH keys when using the Docker executor
+
+You will first need to create an SSH key pair. For more information, follow the
+instructions to [generate an SSH key](../ssh/README.md).
+
+Then, create a new **Secret Variable** in your project settings on GitLab
+following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY`
+and in the **Value** field paste the content of your _private_ key that you
+created earlier.
+
+Next you need to modify your `.gitlab-ci.yml` with a `before_script` action.
+Add it to the top:
+
+```
+before_script:
+  # Install ssh-agent if not already installed, it is required by Docker.
+  # (change apt-get to yum if you use a CentOS-based image)
+  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
+
+  # Run ssh-agent (inside the build environment)
+  - eval $(ssh-agent -s)
+
+  # Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store
+  - ssh-add <(echo "$SSH_PRIVATE_KEY")
+
+  # For Docker builds disable host key checking. Be aware that by adding that
+  # you are suspectible to man-in-the-middle attacks.
+  # WARNING: Use this only with the Docker executor, if you use it with shell
+  # you will overwrite your user's SSH config.
+  - mkdir -p ~/.ssh
+  - '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config`
+```
+
+As a final step, add the _public_ key from the one you created earlier to the
+services that you want to have an access to from within the build environment.
+If you are accessing a private GitLab repository you need to add it as a
+[deploy key](../ssh/README.md#deploy-keys).
+
+That's it! You can now have access to private servers or repositories in your
+build environment.
+
+## SSH keys when using the Shell executor
+
+If you are using the Shell executor and not Docker, it is easier to set up an
+SSH key.
+
+You can generate the SSH key from the machine that GitLab Runner is installed
+on, and use that key for all projects that are run on this machine.
+
+First, you need to login to the server that runs your builds.
+
+Then from the terminal login as the `gitlab-runner` user and generate the SSH
+key pair as described in the [SSH keys documentation](../ssh/README.md).
+
+As a final step, add the _public_ key from the one you created earlier to the
+services that you want to have an access to from within the build environment.
+If you are accessing a private GitLab repository you need to add it as a
+[deploy key](../ssh/README.md#deploy-keys).
+
+Once done, try to login to the remote server in order to accept the fingerprint:
+
+```bash
+ssh <address-of-my-server>
+```
+
+For accessing repositories on GitLab.com, the `<address-of-my-server>` would be
+`git@gitlab.com`.
+
+## Example project
+
+We have set up an [Example SSH Project][ssh-example-repo] for your convenience
+that runs on [GitLab.com](https://gitlab.com) using our publicly available
+[shared runners](../runners/README.md).
+
+Want to hack on it? Simply fork it, commit and push your changes. Within a few
+moments the changes will be picked by a public runner and the build will begin.
+
+[ssh-keygen]: http://linux.die.net/man/1/ssh-keygen
+[ssh-agent]: http://linux.die.net/man/1/ssh-agent
+[ssh-example-repo]: https://gitlab.com/gitlab-examples/ssh-private-key/
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..9f7c1bfe6a036a22f19e5b00ffbe82b45ca5449b
--- /dev/null
+++ b/doc/ci/triggers/README.md
@@ -0,0 +1,172 @@
+# Triggering Builds through the API
+
+_**Note:** This feature was [introduced][ci-229] in GitLab CE 7.14_
+
+Triggers can be used to force a rebuild of a specific branch, tag or commit,
+with an API call.
+
+## Add a trigger
+
+You can add a new trigger by going to your project's **Settings > Triggers**.
+The **Add trigger** button will create a new token which you can then use to
+trigger a rebuild of this particular project.
+
+Every new trigger you create, gets assigned a different token which you can
+then use inside your scripts or `.gitlab-ci.yml`. You also have a nice
+overview of the time the triggers were last used.
+
+![Triggers page overview](img/triggers_page.png)
+
+## Revoke a trigger
+
+You can revoke a trigger any time by going at your project's
+**Settings > Triggers** and hitting the **Revoke** button. The action is
+irreversible.
+
+## Trigger a build
+
+To trigger a build you need to send a `POST` request to GitLab's API endpoint:
+
+```
+POST /projects/:id/trigger/builds
+```
+
+The required parameters are the trigger's `token` and the Git `ref` on which
+the trigger will be performed. Valid refs are the branch, the tag or the commit
+SHA. The `:id` of a project can be found by [querying the API](../api/projects.md)
+or by visiting the **Triggers** page which provides self-explanatory examples.
+
+When a rebuild is triggered, the information is exposed in GitLab's UI under
+the **Builds** page and the builds are marked as `triggered`.
+
+![Marked rebuilds as triggered on builds page](img/builds_page.png)
+
+---
+
+You can see which trigger caused the rebuild by visiting the single build page.
+The token of the trigger is exposed in the UI as you can see from the image
+below.
+
+![Marked rebuilds as triggered on a single build page](img/trigger_single_build.png)
+
+---
+
+See the [Examples](#examples) section for more details on how to actually
+trigger a rebuild.
+
+## Pass build variables to a trigger
+
+You can pass any number of arbitrary variables in the trigger API call and they
+will be available in GitLab CI so that they can be used in your `.gitlab-ci.yml`
+file. The parameter is of the form:
+
+```
+variables[key]=value
+```
+
+This information is also exposed in the UI.
+
+![Build variables in UI](img/trigger_variables.png)
+
+---
+
+See the [Examples](#examples) section below for more details.
+
+## Examples
+
+Using cURL you can trigger a rebuild with minimal effort, for example:
+
+```bash
+curl -X POST \
+     -F token=TOKEN \
+     -F ref=master \
+     https://gitlab.example.com/api/v3/projects/9/trigger/builds
+```
+
+In this case, the project with ID `9` will get rebuilt on `master` branch.
+
+
+### Triggering a build within `.gitlab-ci.yml`
+
+You can also benefit by using triggers in your `.gitlab-ci.yml`. Let's say that
+you have two projects, A and B, and you want to trigger a rebuild on the `master`
+branch of project B whenever a tag on project A is created. This is the job you
+need to add in project's A `.gitlab-ci.yml`:
+
+```yaml
+build_docs:
+  stage: deploy
+  script:
+  - "curl -X POST -F token=TOKEN -F ref=master https://gitlab.example.com/api/v3/projects/9/trigger/builds"
+  only:
+  - tags
+```
+
+Now, whenever a new tag is pushed on project A, the build will run and the
+`build_docs` job will be executed, triggering a rebuild of project B. The
+`stage: deploy` ensures that this job will run only after all jobs with
+`stage: test` complete successfully.
+
+_**Note:** If your project is public, passing the token in plain text is
+probably not the wisest idea, so you might want to use a
+[secure variable](../variables/README.md#user-defined-variables-secure-variables)
+for that purpose._
+
+### Making use of trigger variables
+
+Using trigger variables can be proven useful for a variety of reasons.
+
+* Identifiable jobs. Since the variable is exposed in the UI you can know
+  why the rebuild was triggered if you pass a variable that explains the
+  purpose.
+* Conditional job processing. You can have conditional jobs that run whenever
+  a certain variable is present.
+
+Consider the following `.gitlab-ci.yml` where we set three
+[stages](../yaml/README.md#stages) and the `upload_package` job is run only
+when all jobs from the test and build stages pass. When the `UPLOAD_TO_S3`
+variable is non-zero, `make upload` is run.
+
+```yaml
+stages:
+- test
+- build
+- package
+
+run_tests:
+  script:
+  - make test
+
+build_package:
+  stage: build
+  script:
+  - make build
+
+upload_package:
+  stage: package
+  script:
+  - if [ -n "${UPLOAD_TO_S3}" ]; then make upload; fi
+```
+
+You can then trigger a rebuild while you pass the `UPLOAD_TO_S3` variable
+and the script of the `upload_package` job will run:
+
+```bash
+curl -X POST \
+  -F token=TOKEN \
+  -F ref=master \
+  -F "variables[UPLOAD_TO_S3]=true" \
+  https://gitlab.example.com/api/v3/projects/9/trigger/builds
+```
+
+### Using cron to trigger nightly builds
+
+Whether you craft a script or just run cURL directly, you can trigger builds
+in conjunction with cron. The example below triggers a build on the `master`
+branch of project with ID `9` every night at `00:30`:
+
+```bash
+30 0 * * * curl -X POST -F token=TOKEN -F ref=master https://gitlab.example.com/api/v3/projects/9/trigger/builds
+```
+
+[ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229
diff --git a/doc/ci/triggers/img/builds_page.png b/doc/ci/triggers/img/builds_page.png
new file mode 100644
index 0000000000000000000000000000000000000000..e78794fbee767d82382d2d97e7c9f91a9d9c09c7
Binary files /dev/null and b/doc/ci/triggers/img/builds_page.png differ
diff --git a/doc/ci/triggers/img/trigger_single_build.png b/doc/ci/triggers/img/trigger_single_build.png
new file mode 100644
index 0000000000000000000000000000000000000000..c25f27409d65ce5548cd26940cc568c62fcd5425
Binary files /dev/null and b/doc/ci/triggers/img/trigger_single_build.png differ
diff --git a/doc/ci/triggers/img/trigger_variables.png b/doc/ci/triggers/img/trigger_variables.png
new file mode 100644
index 0000000000000000000000000000000000000000..2207e8b34cbd6b42970e3e47e0740973e0ca655f
Binary files /dev/null and b/doc/ci/triggers/img/trigger_variables.png differ
diff --git a/doc/ci/triggers/img/triggers_page.png b/doc/ci/triggers/img/triggers_page.png
new file mode 100644
index 0000000000000000000000000000000000000000..268368dc3c5b7c6046c2c15f0819641699e9d9a6
Binary files /dev/null and b/doc/ci/triggers/img/triggers_page.png differ
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 022afb700424c25eab121cc08d00e667d4999e7f..b99ea25a3fe33ef74ceef3a9a4202452e897e95e 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -27,7 +27,6 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
 | **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_BEFORE_SHA** | all | The first commit that were included in push request |
 | **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 |
@@ -40,7 +39,6 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
 Example values:
 
 ```bash
-export CI_BUILD_BEFORE_SHA="9df57456fa9de2a6d335ca5edf9750ed812b9df0"
 export CI_BUILD_ID="50"
 export CI_BUILD_REF="1ecfd275763eff1d6b4844ea3168962458c9f27a"
 export CI_BUILD_REF_NAME="master"
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 3dbf1afc7a96ca8e7b2730be1e3e98f8620d5f92..fd0d49de4e438ba654ee160276733cb38768069d 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1,9 +1,12 @@
 # Configuration of your builds with .gitlab-ci.yml
-From version 7.12, GitLab CI uses a [YAML](https://en.wikipedia.org/wiki/YAML) file (**.gitlab-ci.yml**) for the project configuration.
-It is placed in the root of your repository and contains definitions of how your project should be built.
 
-The YAML file defines a set of jobs with constraints stating when they should be run.
-The jobs are defined as top-level elements with a name and always have to contain the `script` clause:
+From version 7.12, GitLab CI uses a [YAML](https://en.wikipedia.org/wiki/YAML)
+file (`.gitlab-ci.yml`) for the project configuration. It is placed in the root
+of your repository and contains definitions of how your project should be built.
+
+The YAML file defines a set of jobs with constraints stating when they should
+be run. The jobs are defined as top-level elements with a name and always have
+to contain the `script` clause:
 
 ```yaml
 job1:
@@ -13,15 +16,21 @@ job2:
   script: "execute-script-for-job2"
 ```
 
-The above example is the simplest possible CI configuration with two separate jobs,
-where each of the jobs executes a different command.
-Of course a command can execute code directly (`./configure;make;make install`) or run a script (`test.sh`) in the repository.
+The above example is the simplest possible CI configuration with two separate
+jobs, where each of the jobs executes a different command.
+
+Of course a command can execute code directly (`./configure;make;make install`)
+or run a script (`test.sh`) in the repository.
 
-Jobs are used to create builds, which are then picked up by [runners](../runners/README.md) and executed within the environment of the runner.
-What is important, is that each job is run independently from each other.
+Jobs are used to create builds, which are then picked up by
+[runners](../runners/README.md) and executed within the environment of the
+runner. What is important, is that each job is run independently from each
+other.
 
 ## .gitlab-ci.yml
-The YAML syntax allows for using more complex job specifications than in the above example:
+
+The YAML syntax allows for using more complex job specifications than in the
+above example:
 
 ```yaml
 image: ruby:2.1
@@ -46,26 +55,31 @@ job1:
     - docker
 ```
 
-There are a few `keywords` that can't be used as job names:
+There are a few reserved `keywords` that **cannot** be used as job names:
 
-| keyword       | required | description |
+| Keyword       | Required | Description |
 |---------------|----------|-------------|
-| image         | optional | Use docker image, covered in [Use Docker](../docker/README.md) |
-| services      | optional | Use docker services, covered in [Use Docker](../docker/README.md) |
-| stages        | optional | Define build stages |
-| types         | optional | Alias for `stages` |
-| before_script | optional | Define commands prepended for each job's script |
-| variables     | optional | Define build variables |
-| cache         | optional | Define list of files that should be cached between subsequent runs |
+| image         | no | Use docker image, covered in [Use Docker](../docker/README.md) |
+| services      | no | Use docker services, covered in [Use Docker](../docker/README.md) |
+| stages        | no | Define build stages |
+| types         | no | Alias for `stages` |
+| before_script | no | Define commands that run before each job's script |
+| variables     | no | Define build variables |
+| cache         | no | Define list of files that should be cached between subsequent runs |
 
 ### image and services
-This allows to specify a custom Docker image and a list of services that can be used for time of the build.
-The configuration of this feature is covered in separate document: [Use Docker](../docker/README.md).
+
+This allows to specify a custom Docker image and a list of services that can be
+used for time of the build. The configuration of this feature is covered in
+separate document: [Use Docker](../docker/README.md).
 
 ### before_script
-`before_script` is used to define the command that should be run before all builds, including deploy builds. This can be an array or a multiline string.
+
+`before_script` is used to define the command that should be run before all
+builds, including deploy builds. This can be an array or a multi-line string.
 
 ### stages
+
 `stages` is used to define build stages that can be used by jobs.
 The specification of `stages` allows for having flexible multi stage pipelines.
 
@@ -75,7 +89,8 @@ The ordering of elements in `stages` defines the ordering of builds' execution:
 1. Builds of next stage are run after success.
 
 Let's consider the following example, which defines 3 stages:
-```
+
+```yaml
 stages:
   - build
   - test
@@ -86,21 +101,26 @@ stages:
 1. If all jobs of `build` succeeds, the `test` jobs are executed in parallel.
 1. If all jobs of `test` succeeds, the `deploy` jobs are executed in parallel.
 1. If all jobs of `deploy` succeeds, the commit is marked as `success`.
-1. If any of the previous jobs fails, the commit is marked as `failed` and no jobs of further stage are executed.
+1. If any of the previous jobs fails, the commit is marked as `failed` and no
+   jobs of further stage are executed.
 
 There are also two edge cases worth mentioning:
 
-1. If no `stages` is defined in `.gitlab-ci.yml`, then by default the `build`, `test` and `deploy` are allowed to be used as job's stage by default.
+1. If no `stages` is defined in `.gitlab-ci.yml`, then by default the `build`,
+   `test` and `deploy` are allowed to be used as job's stage by default.
 2. If a job doesn't specify `stage`, the job is assigned the `test` stage.
 
 ### types
+
 Alias for [stages](#stages).
 
 ### variables
-**This feature requires `gitlab-runner` with version equal or greater than 0.5.0.**
 
-GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build environment.
-The variables are stored in repository and are meant to store non-sensitive project configuration, ie. RAILS_ENV or DATABASE_URL.
+_**Note:** Introduced in GitLab Runner v0.5.0._
+
+GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build
+environment. The variables are stored in the git repository and are meant to
+store non-sensitive project configuration, for example:
 
 ```yaml
 variables:
@@ -109,15 +129,23 @@ variables:
 
 These variables can be later used in all executed commands and scripts.
 
-The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them.
+The YAML-defined variables are also set to all created service containers,
+thus allowing to fine tune them.
 
 ### cache
-`cache` is used to specify list of files and directories which should be cached between builds.
 
-**The global setting allows to specify default cached files for all jobs.**
+`cache` is used to specify a list of files and directories which should be
+cached between builds. Caches are stored according to the branch/ref and the
+job name. They are not currently shared between different job names or between
+branches/refs, which means that caching will benefit you if you push subsequent
+commits to an existing feature branch.
+
+If `cache` is defined outside the scope of the jobs, it means it is set
+globally and all jobs will use its definition.
 
 To cache all git untracked files and files in `binaries`:
-```
+
+```yaml
 cache:
   untracked: true
   paths:
@@ -125,9 +153,10 @@ cache:
 ```
 
 ## Jobs
-`.gitlab-ci.yml` allows you to specify an unlimited number of jobs.
-Each job has to have a unique `job_name`, which is not one of the keywords mentioned above.
-A job is defined by a list of parameters that define the build behaviour.
+
+`.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
+must have a unique name, which is not one of the Keywords mentioned above.
+A job is defined by a list of parameters that define the build behavior.
 
 ```yaml
 job_name:
@@ -145,21 +174,22 @@ job_name:
   allow_failure: true
 ```
 
-| keyword       | required | description |
+| Keyword       | Required | Description |
 |---------------|----------|-------------|
-| script        | required | Defines a shell script which is executed by runner |
-| stage         | optional (default: test) | Defines a build stage |
-| type          | optional | Alias for `stage` |
-| only          | optional | Defines a list of git refs for which build is created |
-| except        | optional | Defines a list of git refs for which build is not created |
-| tags          | optional | Defines a list of tags which are used to select runner |
-| allow_failure | optional | Allow build to fail. Failed build doesn't contribute to commit status |
-| when          | optional | Define when to run build. Can be `on_success`, `on_failure` or `always` |
-| artifacts     | optional | Define list build artifacts |
-| cache         | optional | Define list of files that should be cached between subsequent runs |
+| script        | yes | Defines a shell script which is executed by runner |
+| stage         | no (default: `test`) | Defines a build stage |
+| type          | no | Alias for `stage` |
+| only          | no | Defines a list of git refs for which build is created |
+| except        | no | Defines a list of git refs for which build is not created |
+| tags          | no | Defines a list of tags which are used to select runner |
+| allow_failure | no | Allow build to fail. Failed build doesn't contribute to commit status |
+| when          | no | Define when to run build. Can be `on_success`, `on_failure` or `always` |
+| artifacts     | no | Define list build artifacts |
+| cache         | no | Define list of files that should be cached between subsequent runs |
 
 ### script
-`script` is a shell script which is executed by runner. The shell script is prepended with `before_script`.
+
+`script` is a shell script which is executed by the runner. For example:
 
 ```yaml
 job:
@@ -167,6 +197,7 @@ job:
 ```
 
 This parameter can also contain several commands using an array:
+
 ```yaml
 job:
   script:
@@ -175,31 +206,45 @@ job:
 ```
 
 ### stage
-`stage` allows to group build into different stages. Builds of the same `stage` are executed in `parallel`.
-For more info about the use of `stage` please check the [stages](#stages).
+
+`stage` allows to group build into different stages. Builds of the same `stage`
+are executed in `parallel`. For more info about the use of `stage` please check
+[stages](#stages).
 
 ### only and except
-This are two parameters that allow for setting a refs policy to limit when jobs are built:
-1. `only` defines the names of branches and tags for which job will be built.
-2. `except` defines the names of branches and tags for which the job wil **not** be built.
 
-There are a few rules that apply to usage of refs policy:
+`only` and `except` are two parameters that set a refs policy to limit when
+jobs are built:
 
-1. `only` and `except` are inclusive. If both `only` and `except` are defined in job specification the ref is filtered by `only` and `except`.
-1. `only` and `except` allow for using the regexp expressions.
-1. `only` and `except` allow for using special keywords: `branches` and `tags`.
-These names can be used for example to exclude all tags and all branches.
+1. `only` defines the names of branches and tags for which the job will be
+    built.
+2. `except` defines the names of branches and tags for which the job will
+    **not** be built.
+
+There are a few rules that apply to the usage of refs policy:
+
+* `only` and `except` are inclusive. If both `only` and `except` are defined
+   in a job specification, the ref is filtered by `only` and `except`.
+* `only` and `except` allow the use of regular expressions.
+* `only` and `except` allow the use of special keywords: `branches` and `tags`.
+* `only` and `except` allow to specify a repository path to filter jobs for
+   forks.
+
+In the example below, `job` will run only for refs that start with `issue-`,
+whereas all branches will be skipped.
 
 ```yaml
 job:
+  # use regexp
   only:
-    - /^issue-.*$/ # use regexp
+    - /^issue-.*$/
+  # use special keyword
   except:
-    - branches # use special keyword
+    - branches
 ```
 
-1. `only` and `except` allow for specify repository path to filter jobs for forks.
-The repository path can be used to have jobs executed only for parent repository.
+The repository path can be used to have jobs executed only for the parent
+repository and not forks:
 
 ```yaml
 job:
@@ -208,33 +253,47 @@ job:
   except:
     - master@gitlab-org/gitlab-ce
 ```
-The above will run `job` for all branches on `gitlab-org/gitlab-ce`, except master .
+
+The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
+except master.
 
 ### tags
-`tags` is used to select specific runners from the list of all runners that are allowed to run this project.
 
-During registration of a runner, you can specify the runner's tags, ie.: `ruby`, `postgres`, `development`.
-`tags` allow you to run builds with runners that have the specified tags assigned:
+`tags` is used to select specific runners from the list of all runners that are
+allowed to run this project.
 
-```
+During the registration of a runner, you can specify the runner's tags, for
+example `ruby`, `postgres`, `development`.
+
+`tags` allow you to run builds with runners that have the specified tags
+assigned to them:
+
+```yaml
 job:
   tags:
     - ruby
     - postgres
 ```
 
-The above specification will make sure that `job` is built by a runner that have `ruby` AND `postgres` tags defined.
+The specification above, will make sure that `job` is built by a runner that
+has both `ruby` AND `postgres` tags defined.
 
 ### when
-`when` is used to implement jobs that are run in case of failure or despite the failure.
+
+`when` is used to implement jobs that are run in case of failure or despite the
+failure.
 
 `when` can be set to one of the following values:
 
-1. `on_success` - execute build only when all builds from prior stages succeeded. This is the default.
-1. `on_failure` - execute build only when at least one build from prior stages failed.
+1. `on_success` - execute build only when all builds from prior stages
+    succeeded. This is the default.
+1. `on_failure` - execute build only when at least one build from prior stages
+    failed.
 1. `always` - execute build despite the status of builds from prior stages.
 
-```
+For example:
+
+```yaml
 stages:
 - build
 - cleanup_build
@@ -242,28 +301,28 @@ stages:
 - deploy
 - cleanup
 
-build:
+build_job:
   stage: build
   script:
   - make build
 
-cleanup_build:
+cleanup_build_job:
   stage: cleanup_build
   script:
   - cleanup build when failed
   when: on_failure
 
-test:
+test_job:
   stage: test
   script:
   - make test
 
-deploy:
+deploy_job:
   stage: deploy
   script:
   - make deploy
 
-cleanup:
+cleanup_job:
   stage: cleanup
   script:
   - cleanup after builds
@@ -271,84 +330,108 @@ cleanup:
 ```
 
 The above script will:
-1. Execute `cleanup_build` only when the `build` failed,
-2. Always execute `cleanup` 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.
 
 ### artifacts
-`artifacts` is used to specify list of files and directories which should be attached to build after success.
 
-1. Send all files in `binaries` and `.config`:
+_**Note:** Introduced in GitLab Runner v0.7.0. Also, the Windows shell executor
+ does not currently support artifact uploads._
 
-        artifacts:
-          paths:
-          - binaries/
-          - .config
+`artifacts` is used to specify list of files and directories which should be
+attached to build after success. Below are some examples.
 
-2. Send all git untracked files:
+Send all files in `binaries` and `.config`:
 
-        artifacts:
-          untracked: true
+```yaml
+artifacts:
+  paths:
+  - binaries/
+  - .config
+```
 
-3. Send all git untracked files and files in `binaries`:
+Send all git untracked files:
 
-        artifacts:
-          untracked: true
-          paths:
-          - binaries/
+```yaml
+artifacts:
+  untracked: true
+```
+
+Send all git untracked files and files in `binaries`:
 
-The artifacts will be send after the build success to GitLab and will be accessible in GitLab interface to download.
+```yaml
+artifacts:
+  untracked: true
+  paths:
+  - binaries/
+```
 
-This feature requires GitLab Runner v0.7.0 or higher.
+The artifacts will be send after a successful build success to GitLab, and will
+be accessible in the GitLab UI to download.
 
 ### cache
-`cache` is used to specify list of files and directories which should be cached between builds.
 
-1. Cache all files in `binaries` and `.config`:
+_**Note:** Introduced in GitLab Runner v0.7.0._
 
-        rspec:
-          script: test
-          cache:
-            paths:
-            - binaries/
-            - .config
+`cache` is used to specify list of files and directories which should be cached
+between builds. Below are some examples:
 
-2. Cache all git untracked files:
+Cache all files in `binaries` and `.config`:
 
-        rspec:
-          script: test
-          cache:
-            untracked: true
-            
-3. Cache all git untracked files and files in `binaries`:
+```yaml
+rspec:
+  script: test
+  cache:
+    paths:
+    - binaries/
+    - .config
+```
 
-        rspec:
-          script: test
-          cache:
-            untracked: true
-            paths:
-            - binaries/
+Cache all git untracked files:
 
-4. Locally defined cache overwrites globally defined options. This will cache only `binaries/`:
+```yaml
+rspec:
+  script: test
+  cache:
+    untracked: true
+```
+
+Cache all git untracked files and files in `binaries`:
+
+```yaml
+rspec:
+  script: test
+  cache:
+    untracked: true
+    paths:
+    - binaries/
+```
 
-        cache:
-          paths:
-          - my/files
-        
-        rspec:
-          script: test
-          cache:
-            paths:
-            - binaries/
+Locally defined cache overwrites globally defined options. This will cache only
+`binaries/`:
 
-The cache is provided on best effort basis, so don't expect that cache will be present.
-For implementation details please check GitLab Runner.
+```yaml
+cache:
+  paths:
+  - my/files
 
-This feature requires GitLab Runner v0.7.0 or higher.
+rspec:
+  script: test
+  cache:
+    paths:
+    - binaries/
+```
 
+The cache is provided on best effort basis, so don't expect that cache will be
+always present. For implementation details please check GitLab Runner.
 
 ## Validate the .gitlab-ci.yml
+
 Each instance of GitLab CI has an embedded debug tool called Lint.
-You can find the link to the Lint in the project's settings page or use short url `/lint`.
+You can find the link under `/ci/lint` of your gitlab instance.
 
 ## Skipping builds
-There is one more way to skip all builds, if your commit message contains tag [ci skip]. In this case, commit will be created but builds will be skipped
+
+If your commit message contains `[ci skip]`, the commit will be created but the
+builds will be skipped.
diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md
index 64f128f5a637548adc2d820b43c216976a63feb3..00edfc97ed95ce6e0f8cc3d948816335d4d2874e 100644
--- a/doc/customization/issue_closing.md
+++ b/doc/customization/issue_closing.md
@@ -1,38 +1,39 @@
 # Issue closing pattern
 
-Here's how to close multiple issues in one commit message:
+When a commit or merge request resolves one or more issues, it is possible to automatically have these issues closed when the commit or merge request lands in the project's default branch.
 
-If a commit message matches the regular expression below, all issues referenced from
-the matched text will be closed. This happens when the commit is pushed or merged
-into the default branch of a project.
+If a commit message or merge request description contains a sentence matching the regular expression below, all issues referenced from
+the matched text will be closed. This happens when the commit is pushed to a project's default branch, or when a commit or merge request is merged into there.
 
-When not specified, the default issue_closing_pattern as shown below will be used:
+When not specified, the default `issue_closing_pattern` as shown below will be used:
 
 ```bash
-((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)
+((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)
 ```
 
+Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that matches a reference to a local issue (`#123`), cross-project issue (`group/project#123`) or a link to an issue (`https://gitlab.example.com/group/project/issues/123`).
+
 For example:
 
 ```
-git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes #22). This commit is also related to #17 and fixes #18, #19 and #23."
+git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#2). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23."
 ```
 
-will close `#20`, `#21`, `#22`, `#18`, `#19` and `#23`, but `#17` won't be closed
-as it does not match the pattern. It also works with multiline commit messages.
+will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages.
 
 Tip: you can test this closing pattern at [http://rubular.com][1]. Use this site
 to test your own patterns.
+Because Rubular doesn't understand `%{issue_ref}`, you can replace this by `#\d+` in testing, which matches only local issue references like `#123`.
 
 ## Change the pattern
 
 For Omnibus installs you can change the default pattern in `/etc/gitlab/gitlab.rb`:
 
 ```
-issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)'
+issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)'
 ```
 
-For manual installs you can customize the pattern in [gitlab.yml][0].
+For manual installs you can customize the pattern in [gitlab.yml][0] using the `issue_closing_pattern` key.
 
-[0]: https://gitlab.com/gitlab-org/gitlab-ce/blob/40c3675372320febf5264061c9bcd63db2dfd13c/config/gitlab.yml.example#L65
-[1]: http://rubular.com/r/Xmbexed1OJ
\ No newline at end of file
+[0]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example
+[1]: http://rubular.com/r/Xmbexed1OJ
diff --git a/doc/development/db_dump.md b/doc/development/db_dump.md
index 21f1b3edecd7df65b3b5b9efeedd618cb26ef2a7..e4ff72aa349eaa6799d13a72cdc43186f90758bf 100644
--- a/doc/development/db_dump.md
+++ b/doc/development/db_dump.md
@@ -1,4 +1,4 @@
-# Importing a database dump into a staging enviroment
+# Importing a database dump into a staging environment
 
 Sometimes it is useful to import the database from a production environment
 into a staging environment for testing. The procedure below assumes you have
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 3f5c03a890af82daeea2cf947597b8b9b1cc509a..81edd8da2b845f055bba5f964dc6b3c98a979ac5 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -38,6 +38,7 @@ The GitLab installation consists of setting up the following components:
 
 1. Packages / Dependencies
 1. Ruby
+1. Go
 1. System Users
 1. Database
 1. Redis
@@ -62,7 +63,7 @@ up-to-date and install it.
 
 Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
 
-    sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake nodejs
+    sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake nodejs
 
 If you want to use Kerberos for user authentication, then install libkrb5-dev:
 
@@ -174,33 +175,53 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
 
 ## 6. Redis
 
-    sudo apt-get install redis-server
+As of this writing, most Debian/Ubuntu distributions ship with Redis 2.2 or
+2.4. GitLab requires at least Redis 2.8.
 
-    # Configure redis to use sockets
-    sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
+Ubuntu users [can use a PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/redis-server)
+to install a recent version of Redis.
 
-    # Disable Redis listening on TCP by setting 'port' to 0
-    sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf
+The following instructions cover building and installing Redis from scratch:
 
-    # Enable Redis socket for default Debian / Ubuntu path
-    echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
-    # Grant permission to the socket to all members of the redis group
-    echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf
+```sh
+# Build Redis
+wget http://download.redis.io/releases/redis-2.8.23.tar.gz
+tar xzf redis-2.8.23.tar.gz
+cd redis-2.8.23
+make
 
-    # Create the directory which contains the socket
-    mkdir /var/run/redis
-    chown redis:redis /var/run/redis
-    chmod 755 /var/run/redis
-    # Persist the directory which contains the socket, if applicable
-    if [ -d /etc/tmpfiles.d ]; then
-      echo 'd  /var/run/redis  0755  redis  redis  10d  -' | sudo tee -a /etc/tmpfiles.d/redis.conf
-    fi
+# Install Redis
+cd utils
+sudo ./install_server.sh
 
-    # Activate the changes to redis.conf
-    sudo service redis-server restart
+# Configure redis to use sockets
+sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
 
-    # Add git to the redis group
-    sudo usermod -aG redis git
+# Disable Redis listening on TCP by setting 'port' to 0
+sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf
+
+# Enable Redis socket for default Debian / Ubuntu path
+echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
+
+# Grant permission to the socket to all members of the redis group
+echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf
+
+# Create the directory which contains the socket
+mkdir /var/run/redis
+chown redis:redis /var/run/redis
+chmod 755 /var/run/redis
+
+# Persist the directory which contains the socket, if applicable
+if [ -d /etc/tmpfiles.d ]; then
+  echo 'd  /var/run/redis  0755  redis  redis  10d  -' | sudo tee -a /etc/tmpfiles.d/redis.conf
+fi
+
+# Activate the changes to redis.conf
+sudo service redis_6379 start
+
+# Add git to the redis group
+sudo usermod -aG redis git
+```
 
 ## 7. GitLab
 
@@ -210,9 +231,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
 ### Clone the Source
 
     # Clone GitLab repository
-    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-2-stable gitlab
+    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-3-stable gitlab
 
-**Note:** You can change `8-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `8-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
 
 ### Configure It
 
@@ -312,7 +333,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
 GitLab Shell is an SSH access and repository management software developed specially for GitLab.
 
     # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
-    sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.7] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
+    sudo -u git -H bundle exec rake gitlab:shell:install REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
 
     # By default, the gitlab-shell config is generated from your main GitLab config.
     # You can review (and modify) the gitlab-shell config as follows:
@@ -327,7 +348,7 @@ GitLab Shell is an SSH access and repository management software developed speci
     cd /home/git
     sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
     cd gitlab-workhorse
-    sudo -u git -H git checkout 0.4.2
+    sudo -u git -H git checkout 0.5.1
     sudo -u git -H make
 
 ### Initialize Database and Activate Advanced Features
diff --git a/doc/integration/README.md b/doc/integration/README.md
index eff39a626ae6205a122558fd5f8990882d930b04..2a9f76533b733a224af1389fef13caf00dac73ef 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -4,13 +4,16 @@ GitLab integrates with multiple third-party services to allow external issue tra
 
 See the documentation below for details on how to configure these services.
 
+- [Jira](jira.md) Integrate with the JIRA issue tracker
 - [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
 - [LDAP](ldap.md) Set up sign in via LDAP
 - [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth.
 - [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
 
 GitLab Enterprise Edition contains [advanced JIRA support](http://doc.gitlab.com/ee/integration/jira.html) and [advanced Jenkins support](http://doc.gitlab.com/ee/integration/jenkins.html).
 
diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md
index 6a0fa4ce015e4a48f1c130bddf60e9584fa32b3b..63432b044323a1d7d8f430d631baf71e165fe3ca 100644
--- a/doc/integration/bitbucket.md
+++ b/doc/integration/bitbucket.md
@@ -30,7 +30,7 @@ Bitbucket will generate an application ID and secret key for you to use.
       sudo editor /etc/gitlab/gitlab.rb
     ```
 
-    For instalations from source:
+    For installations from source:
 
     ```sh
       cd /home/git/gitlab
diff --git a/doc/integration/cas.md b/doc/integration/cas.md
new file mode 100644
index 0000000000000000000000000000000000000000..e6b2071f193cd5ca5e424960f4ac9a198de7fbcd
--- /dev/null
+++ b/doc/integration/cas.md
@@ -0,0 +1,62 @@
+# CAS OmniAuth Provider
+
+To enable the CAS OmniAuth provider you must register your application with your CAS instance. This requires the service URL GitLab will supply to CAS. It should be something like: `https://gitlab.example.com:443/users/auth/cas3/callback?url`. By default handling for SLO is enabled, you only need to configure CAS for backchannel logout.
+
+1.  On your GitLab server, open the configuration file.
+
+    For omnibus package:
+
+    ```sh
+      sudo editor /etc/gitlab/gitlab.rb
+    ```
+
+    For installations from source:
+
+    ```sh
+      cd /home/git/gitlab
+
+      sudo -u git -H editor config/gitlab.yml
+    ```
+
+1.  See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+
+1.  Add the provider configuration:
+
+    For omnibus package:
+
+    ```ruby
+      gitlab_rails['omniauth_providers'] = [
+        {
+          name: "cas3",
+          label: "cas",
+          args: {
+                  url: 'CAS_SERVER',
+                  login_url: '/CAS_PATH/login',
+                  service_validate_url: '/CAS_PATH/p3/serviceValidate',
+                  logout_url: '/CAS_PATH/logout'} }
+          }
+        }
+      ]
+    ```
+
+    For installations from source:
+
+    ```
+      - { name: 'cas3',
+          label: 'cas',
+          args: {
+                  url: 'CAS_SERVER',
+                  login_url: '/CAS_PATH/login',
+                  service_validate_url: '/CAS_PATH/p3/serviceValidate',
+                  logout_url: '/CAS_PATH/logout'} }
+    ```
+
+1.  Change 'CAS_PATH' to the root of your CAS instance (ie. `cas`).
+
+1.  If your CAS instance does not use default TGC lifetimes, update the `cas3.session_duration` to at least the current TGC maximum lifetime. To explicitly disable SLO, regardless of CAS settings, set this to 0.
+
+1.  Save the configuration file.
+
+1.  Restart GitLab for the changes to take effect.
+
+On the sign in page there should now be a CAS tab in the sign in form.
diff --git a/doc/integration/crowd.md b/doc/integration/crowd.md
index 2ecc8795ac1d8d34bf64074d86966ece904c13c5..40d93aef2a932aed4f5d3dca84048f87640cdf0a 100644
--- a/doc/integration/crowd.md
+++ b/doc/integration/crowd.md
@@ -10,7 +10,7 @@ To enable the Crowd OmniAuth provider you must register your application with Cr
       sudo editor /etc/gitlab/gitlab.rb
     ```
 
-    For instalations from source:
+    For installations from source:
 
     ```sh
       cd /home/git/gitlab
diff --git a/doc/integration/github.md b/doc/integration/github.md
index b64501c2aaab8ec3f32c91c70426c158f69a4955..a789d2c814f5b6a2d7c6a4f513f25f9a63d55b67 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -32,7 +32,7 @@ GitHub will generate an application ID and secret key for you to use.
       sudo editor /etc/gitlab/gitlab.rb
     ```
 
-    For instalations from source:
+    For installations from source:
 
     ```sh
       cd /home/git/gitlab
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
index 216f1f11a9b0c9b1d9c364305364f0f0216cab8a..80e3c0142a07cdc56bbae01529679071d3bb7a6e 100644
--- a/doc/integration/gitlab.md
+++ b/doc/integration/gitlab.md
@@ -38,7 +38,7 @@ GitLab.com will generate an application ID and secret key for you to use.
       sudo editor /etc/gitlab/gitlab.rb
     ```
 
-    For instalations from source:
+    For installations from source:
 
     ```sh
       cd /home/git/gitlab
diff --git a/doc/integration/google.md b/doc/integration/google.md
index e1c14c7c94828f08b04254a7427f65b18b809a38..91e9b2495cc603ddba294776f633cd855103b2dd 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -35,7 +35,7 @@ To enable the Google OAuth2 OmniAuth provider you must register your application
       sudo editor /etc/gitlab/gitlab.rb
     ```
 
-    For instalations from source:
+    For installations from source:
 
     ```sh
       cd /home/git/gitlab
diff --git a/doc/integration/jira.md b/doc/integration/jira.md
new file mode 100644
index 0000000000000000000000000000000000000000..624601d0faccb8e0192d7d7865168a9da0798255
--- /dev/null
+++ b/doc/integration/jira.md
@@ -0,0 +1,113 @@
+# GitLab Jira integration
+
+GitLab can be configured to interact with Jira.
+Configuration happens via username and password.
+Connecting to a Jira server via CAS is not possible.
+
+Each project can be configured to connect to a different Jira instance, configuration is explained [here](#configuration).
+If you have one Jira instance you can pre-fill the settings page with a default template. To configure the template [see external issue tracker document](external-issue-tracker.md#service-template)).
+
+Once the project is connected to Jira, you can reference and close the issues in Jira directly from GitLab.
+
+
+## Table of Contents
+
+* [Referencing Jira Issues from GitLab](#referencing-jira-issues)
+* [Closing Jira Issues from GitLab](#closing-jira-issues)
+* [Configuration](#configuration)
+
+### Referencing Jira Issues
+
+When GitLab project has Jira issue tracker configured and enabled, mentioning Jira issue in GitLab will automatically add a comment in Jira issue with the link back to GitLab. This means that in comments in merge requests and commits referencing an issue, eg. `PROJECT-7`, will add a comment in Jira issue in the format:
+
+
+```
+ USER mentioned this issue in LINK_TO_THE_MENTION
+```
+
+* `USER` A user that mentioned the issue. This is the link to the user profile in GitLab.
+* `LINK_TO_THE_MENTION` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
+Can be commit or merge request.
+
+
+![example of mentioning or closing the Jira issue](jira_issue_reference.png)
+
+
+### Closing Jira Issues
+
+Jira issues can be closed directly from GitLab by using trigger words, eg. `Resolves PROJECT-1`, `Closes PROJECT-1` or `Fixes PROJECT-1`, in commits and merge requests.
+When a commit which contains the trigger word in the commit message is pushed, GitLab will add a comment in the mentioned Jira issue.
+
+For example, for project named PROJECT in Jira, we implemented a new feature and created a merge request in GitLab.
+
+This feature was requested in Jira issue PROJECT-7. Merge request in GitLab contains the improvement and in merge request description we say that this merge request `Closes PROJECT-7` issue.
+
+Once this merge request is merged, Jira issue will be automatically closed with a link to the commit that resolved the issue.
+
+![A Git commit that causes the Jira issue to be closed](merge_request_close_jira.png)
+
+
+![The GitLab integration user leaves a comment on Jira](jira_service_close_issue.png)
+
+
+## Configuration
+
+### Configuring JIRA
+
+We need to create a user in JIRA which will have access to all projects that need to integrate with GitLab.
+Login to your JIRA instance as admin and under Administration go to User Management and create a new user.
+As an example, we'll create a user named `gitlab` and add it to `jira-developers` group.
+
+**It is important that the user `gitlab` has write-access to projects in JIRA**
+
+### Configuring GitLab
+
+### GitLab 7.8 EE and up with JIRA v6.x
+
+To enable JIRA integration in a project, navigate to the project Settings page and go to Services. Here you will find JIRA.
+
+Fill in the required details on the page:
+
+![Jira service page](jira_service_page.png)
+
+* `description` A name for the issue tracker (to differentiate between instances, for instance).
+* `project url` The URL to the JIRA project which is being linked to this GitLab project.
+* `issues url` The URL to the JIRA project issues overview for the project that is linked to this GitLab project.
+* `new issue url` This is the URL to create a new issue in JIRA for the project linked to this GitLab project.
+* `api url` The base URL of the JIRA API. It may be omitted, in which case GitLab will automatically use API version `2` based on the `project url`, i.e. `https://jira.example.com/rest/api/2`.
+* `username` The username of the user created in [configuring JIRA step](#configuring-jira).
+* `password` The password of the user created in [configuring JIRA step](#configuring-jira).
+* `Jira issue transition` This is the id of a transition that moves issues to a closed state. You can find this number under [JIRA workflow administration, see screenshot](jira_workflow_screenshot.png).  By default, this id is `2`. (In the example image, this is `2` as well)
+
+After saving the configuration, your GitLab project will be able to interact with the linked JIRA project.
+
+
+### GitLab 6.x-7.7 with JIRA v6.x
+
+**Note: GitLab 7.8 and up contain various integration improvements. We strongly recommend upgrading.**
+
+
+In `gitlab.yml` enable [JIRA issue tracker section by uncommenting the lines](https://gitlab.com/subscribers/gitlab-ee/blob/6-8-stable-ee/config/gitlab.yml.example#L111-115).
+This will make sure that all issues within GitLab are pointing to the JIRA issue tracker.
+
+We can also enable JIRA service that will allow us to interact with JIRA issues.
+
+For example, we can close issues in JIRA by a commit in GitLab.
+
+Go to project settings page and fill in the project name for the JIRA project:
+
+![Set the JIRA project name in GitLab to 'NEW'](jira_project_name.png)
+
+Next, go to the services page and find JIRA.
+
+![Jira services page](jira_service.png)
+
+1. Tick the active check box to enable the service.
+1. Supply the url to JIRA server, for example http://jira.sample
+1. Supply the username of a user we created under `Configuring JIRA` section, for example `gitlab`
+1. Supply the password of the user
+1. Optional: supply the JIRA api version, default is version
+1. Optional: supply the JIRA issue transition ID (issue transition to closed). This is dependant on JIRA settings, default is 2
+1. Save
+
+Now we should be able to interact with JIRA issues.
diff --git a/doc/integration/jira_issue_reference.png b/doc/integration/jira_issue_reference.png
new file mode 100644
index 0000000000000000000000000000000000000000..15739a22dc7151af663316c1d44e8fcda704ce29
Binary files /dev/null and b/doc/integration/jira_issue_reference.png differ
diff --git a/doc/integration/jira_project_name.png b/doc/integration/jira_project_name.png
new file mode 100644
index 0000000000000000000000000000000000000000..5986fdb63fb8727b339b69d7fa6b64d3ef5f4b17
Binary files /dev/null and b/doc/integration/jira_project_name.png differ
diff --git a/doc/integration/jira_service.png b/doc/integration/jira_service.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f6628c43719edd55d415f80d28d8d4423002586
Binary files /dev/null and b/doc/integration/jira_service.png differ
diff --git a/doc/integration/jira_service_close_issue.png b/doc/integration/jira_service_close_issue.png
new file mode 100644
index 0000000000000000000000000000000000000000..67dfc6144c44f0643e1373bef9961bf0c3a458fb
Binary files /dev/null and b/doc/integration/jira_service_close_issue.png differ
diff --git a/doc/integration/jira_service_page.png b/doc/integration/jira_service_page.png
new file mode 100644
index 0000000000000000000000000000000000000000..69ec44e826fb82702103f7cf1fd4299084e24151
Binary files /dev/null and b/doc/integration/jira_service_page.png differ
diff --git a/doc/integration/jira_workflow_screenshot.png b/doc/integration/jira_workflow_screenshot.png
new file mode 100644
index 0000000000000000000000000000000000000000..8635a32eb6874fa64bc53ba671794c20e9df2f00
Binary files /dev/null and b/doc/integration/jira_workflow_screenshot.png differ
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index 7e2920b8865e6aeb7d4dfed2af12cb67640e5360..845f588f913808451ceef55dfb823ddeabcf663e 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -13,6 +13,12 @@ An LDAP user who is allowed to change their email on the LDAP server can [take o
 
 We recommend against using GitLab LDAP integration if your LDAP users are allowed to change their 'mail', 'email' or 'userPrincipalName'  attribute on the LDAP server.
 
+If a user is deleted from the LDAP server, they will be blocked in GitLab as well.
+Users will be immediately blocked from logging in. However, there is an LDAP check
+cache time of one hour. The means users that are already logged in or are using Git
+over SSH will still be able to access GitLab for up to one hour. Manually block
+the user in the GitLab Admin area to immediately block all access.
+
 ## Configuring GitLab for LDAP integration
 
 To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
@@ -192,4 +198,4 @@ Not supported by GitLab's configuration options.
 When setting `method: ssl`, the underlying authentication method used by 
 `omniauth-ldap` is `simple_tls`.  This method establishes TLS encryption with 
 the LDAP server before any LDAP-protocol data is exchanged but no validation of
-the LDAP server's SSL certificate is performed.
\ No newline at end of file
+the LDAP server's SSL certificate is performed.
diff --git a/doc/integration/recaptcha.md b/doc/integration/recaptcha.md
new file mode 100644
index 0000000000000000000000000000000000000000..a301d1a613c3a8e8800911fd0a48a41bccb1f3e3
--- /dev/null
+++ b/doc/integration/recaptcha.md
@@ -0,0 +1,23 @@
+# reCAPTCHA
+
+GitLab leverages [Google's reCAPTCHA](https://www.google.com/recaptcha/intro/index.html)
+to protect against spam and abuse. GitLab displays the CAPTCHA form on the sign-up page
+to confirm that a real user, not a bot, is attempting to create an account.
+
+## Configuration
+
+To use reCAPTCHA, first you must create a site and private key.
+
+1. Go to the URL: https://www.google.com/recaptcha/admin
+
+2. Fill out the form necessary to obtain reCAPTCHA keys.
+
+3. Login to your GitLab server, with administrator credentials.
+
+4. Go to Applications Settings on Admin Area (`admin/application_settings`)
+
+5. Fill all recaptcha fields with keys from previous steps
+
+6. Check the `Enable reCAPTCHA` checkbox
+
+7.  Save the configuration.
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 4aa6dbe758a10c723a7ce030e5719771ac430785..1632e42f701925afcd5400423566d4699a14a75d 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -14,7 +14,7 @@ First configure SAML 2.0 support in GitLab, then register the GitLab application
       sudo editor /etc/gitlab/gitlab.rb
     ```
 
-    For instalations from source:
+    For installations from source:
 
     ```sh
       cd /home/git/gitlab
@@ -38,7 +38,8 @@ First configure SAML 2.0 support in GitLab, then register the GitLab application
                    idp_sso_target_url: 'https://login.example.com/idp',
                    issuer: 'https://gitlab.example.com',
                    name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
-                 }
+                 },
+          "label" => "Company Login" # optional label for SAML login button, defaults to "Saml"
         }
       ]
     ```
@@ -79,4 +80,4 @@ On the sign in page there should now be a SAML button below the regular sign in
 
 If you see a "500 error" in GitLab when you are redirected back from the SAML sign in page, this likely indicates that GitLab could not get the email address for the SAML user.
 
-Make sure the IdP provides a claim containing the user's email address, using claim name 'email' or 'mail'. The email will be used to automatically generate the GitLab username.
+Make sure the IdP provides a claim containing the user's email address, using claim name 'email' or 'mail'. The email will be used to automatically generate the GitLab username.
\ No newline at end of file
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index 1350c8f693c363777ca4a166f5a91c3aa0eb84c1..52ed4a22339845e096b600cb55d7739ba864bbfa 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -37,7 +37,7 @@ To enable the Twitter OmniAuth provider you must register your application with
       sudo editor /etc/gitlab/gitlab.rb
     ```
 
-    For instalations from source:
+    For installations from source:
 
     ```sh
       cd /home/git/gitlab
diff --git a/doc/operations/moving_repositories.md b/doc/operations/moving_repositories.md
new file mode 100644
index 0000000000000000000000000000000000000000..39086b7a2519e24eb3d22e9b5d99fcd592dd09a8
--- /dev/null
+++ b/doc/operations/moving_repositories.md
@@ -0,0 +1,180 @@
+# Moving repositories managed by GitLab
+
+Sometimes you need to move all repositories managed by GitLab to
+another filesystem or another server. In this document we will look
+at some of the ways you can copy all your repositories from
+`/var/opt/gitlab/git-data/repositories` to `/mnt/gitlab/repositories`.
+
+We will look at three scenarios: the target directory is empty, the
+target directory contains an outdated copy of the repositories, and
+how to deal with thousands of repositories.
+
+**Each of the approaches we list can/will overwrite data in the
+target directory `/mnt/gitlab/repositories`. Do not mix up the
+source and the target.**
+
+## Target directory is empty: use a tar pipe
+
+If the target directory `/mnt/gitlab/repositories` is empty the
+simplest thing to do is to use a tar pipe.  This method has low
+overhead and tar is almost always already installed on your system.
+However, it is not possible to resume an interrupted tar pipe:  if
+that happens then all data must be copied again.
+
+```
+# As the git user
+tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\
+  tar -C /mnt/gitlab/repositories -xf -
+```
+
+If you want to see progress, replace `-xf` with `-xvf`.
+
+### Tar pipe to another server
+
+You can also use a tar pipe to copy data to another server. If your
+'git' user has SSH access to the newserver as 'git@newserver', you
+can pipe the data through SSH.
+
+```
+# As the git user
+tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\
+  ssh git@newserver tar -C /mnt/gitlab/repositories -xf -
+```
+
+If you want to compress the data before it goes over the network
+(which will cost you CPU cycles) you can replace `ssh` with `ssh -C`.
+
+## The target directory contains an outdated copy of the repositories: use rsync
+
+If the target directory already contains a partial / outdated copy
+of the repositories it may be wasteful to copy all the data again
+with tar. In this scenario it is better to use rsync. This utility
+is either already installed on your system or easily installable
+via apt, yum etc.
+
+```
+# As the 'git' user
+rsync -a --delete /var/opt/gitlab/git-data/repositories/. \
+  /mnt/gitlab/repositories
+```
+
+The `/.` in the command above is very important, without it you can
+easily get the wrong directory structure in the target directory.
+If you want to see progress, replace `-a` with `-av`.
+
+### Single rsync to another server
+
+If the 'git' user on your source system has SSH access to the target
+server you can send the repositories over the network with rsync.
+
+```
+# As the 'git' user
+rsync -a --delete /var/opt/gitlab/git-data/repositories/. \
+  git@newserver:/mnt/gitlab/repositories
+```
+
+## Thousands of Git repositories: use one rsync per repository
+
+Every time you start an rsync job it has to inspect all files in
+the source directory, all files in the target directory, and then
+decide what files to copy or not. If the source or target directory
+has many contents this startup phase of rsync can become a burden
+for your GitLab server. In cases like this you can make rsync's
+life easier by dividing its work in smaller pieces, and sync one
+repository at a time.
+
+In addition to rsync we will use [GNU
+Parallel](http://www.gnu.org/software/parallel/). This utility is
+not included in GitLab so you need to install it yourself with apt
+or yum.  Also note that the GitLab scripts we used below were added
+in GitLab 8.1.
+
+** This process does not clean up repositories at the target location that no
+longer exist at the source. ** If you start using your GitLab instance with
+`/mnt/gitlab/repositories`, you need to run `gitlab-rake gitlab:cleanup:repos`
+after switching to the new repository storage directory.
+
+### Parallel rsync for all repositories known to GitLab
+
+This will sync repositories with 10 rsync processes at a time. We keep
+track of progress so that the transfer can be restarted if necessary.
+
+First we create a new directory, owned by 'git', to hold transfer
+logs. We assume the directory is empty before we start the transfer
+procedure, and that we are the only ones writing files in it.
+
+```
+# Omnibus
+sudo mkdir /var/opt/gitlab/transfer-logs
+sudo chown git:git /var/opt/gitlab/transfer-logs
+
+# Source
+sudo -u git -H mkdir /home/git/transfer-logs
+```
+
+We seed the process with a list of the directories we want to copy.
+
+```
+# Omnibus
+sudo -u git sh -c 'gitlab-rake gitlab:list_repos > /var/opt/gitlab/transfer-logs/all-repos-$(date +%s).txt'
+
+# Source
+cd /home/git/gitlab
+sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-logs/all-repos-$(date +%s).txt'
+```
+
+Now we can start the transfer. The command below is idempotent, and
+the number of jobs done by GNU Parallel should converge to zero. If it
+does not some repositories listed in all-repos-1234.txt may have been
+deleted/renamed before they could be copied.
+
+```
+# Omnibus
+sudo -u git sh -c '
+cat /var/opt/gitlab/transfer-logs/* | sort | uniq -u |\
+  /usr/bin/env JOBS=10 \
+  /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \
+    /var/opt/gitlab/transfer-logs/succes-$(date +%s).log \
+    /var/opt/gitlab/git-data/repositories \
+    /mnt/gitlab/repositories
+'
+
+# Source
+cd /home/git/gitlab
+sudo -u git -H sh -c '
+cat /home/git/transfer-logs/* | sort | uniq -u |\
+  /usr/bin/env JOBS=10 \
+  bin/parallel-rsync-repos \
+    /home/git/transfer-logs/succes-$(date +%s).log \
+    /home/git/repositories \
+    /mnt/gitlab/repositories
+`
+```
+
+### Parallel rsync only for repositories with recent activity
+
+Suppose you have already done one sync that started after 2015-10-1 12:00 UTC.
+Then you might only want to sync repositories that were changed via GitLab
+_after_ that time. You can use the 'SINCE' variable to tell 'rake
+gitlab:list_repos' to only print repositories with recent activity.
+
+```
+# Omnibus
+sudo gitlab-rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\
+  sudo -u git \
+  /usr/bin/env JOBS=10 \
+  /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \
+    succes-$(date +%s).log \
+    /var/opt/gitlab/git-data/repositories \
+    /mnt/gitlab/repositories
+
+# Source
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\
+  sudo -u git -H \
+  /usr/bin/env JOBS=10 \
+  bin/parallel-rsync-repos \
+    succes-$(date +%s).log \
+    /home/git/repositories \
+    /mnt/gitlab/repositories
+```
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 8d4c2ceab7d25c4a25623010d87ab5487bff40b1..1be78ac1823f4470c40d3e1148ffcd233f25f4af 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -6,8 +6,11 @@ If a user is both in a project group and in the project itself, the highest perm
 
 If a user is a GitLab administrator they receive all permissions.
 
+On public projects the Guest role is not enforced.
+All users will be able to create issues, leave comments, and pull or download the project code.
+
 To add or import a user, you can follow the [project users and members
-documentation](doc/workflow/add-user/add-user.md).
+documentation](../workflow/add-user/add-user.md).
 
 ## Project
 
@@ -15,8 +18,8 @@ documentation](doc/workflow/add-user/add-user.md).
 |---------------------------------------|---------|------------|-------------|----------|--------|
 | Create new issue                      | ✓       | ✓          | ✓           | ✓        | ✓      |
 | Leave comments                        | ✓       | ✓          | ✓           | ✓        | ✓      |
-| Pull project code                     | ✓        | ✓          | ✓           | ✓        | ✓      |
-| Download project                      | ✓        | ✓          | ✓           | ✓        | ✓      |
+| Pull project code                     |         | ✓          | ✓           | ✓        | ✓      |
+| Download project                      |         | ✓          | ✓           | ✓        | ✓      |
 | Create code snippets                  |         | ✓          | ✓           | ✓        | ✓      |
 | Manage issue tracker                  |         | ✓          | ✓           | ✓        | ✓      |
 | Manage labels                         |         | ✓          | ✓           | ✓        | ✓      |
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index bd439f7c6f356856be11fff0e92aa028ed183b13..6e22ea7b72af8780ea6fba1fdb5a46213f8c584a 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -1,44 +1,59 @@
 # Public access
 
-GitLab allows you to open selected projects to be accessed **publicly** or **internally**.
+GitLab allows you to change your projects' visibility in order be accessed
+**publicly** or **internally**.
 
-Projects with either of these visibility levels will be listed in the [public access directory](/public).
+Projects with either of these visibility levels will be listed in the
+public access directory (`/public` under your GitLab instance).
+Here is the [GitLab.com example](https://gitlab.com/public).
 
 Internal projects will only be available to authenticated users.
 
-## Public projects
+## Visibility of projects
+
+### Public projects
 
 Public projects can be cloned **without any** authentication.
 
-It will also be listed on the [public access directory](/public).
+They will also be listed on the public access directory (`/public`).
 
-**Any logged in user** will have [Guest](../permissions/permissions) permissions on the repository.
+**Any logged in user** will have [Guest](../permissions/permissions)
+permissions on the repository.
 
-## Internal projects
+### Internal projects
 
 Internal projects can be cloned by any logged in user.
 
-It will also be listed on the [public access directory](/public) for logged in users.
+They will also be listed on the public access directory (`/public`) for logged
+in users.
 
-Any logged in user will have [Guest](../permissions/permissions) permissions on the repository.
+Any logged in user will have [Guest](../permissions/permissions) permissions on
+the repository.
 
-## How to change project visibility
+### How to change project visibility
 
-1. Go to your project dashboard
-1. Click on the "Edit" tab
-1. Change "Visibility Level"
+1. Go to your project's **Settings**
+1. Change "Visibility Level" to either Public, Internal or Private
 
 ## Visibility of users
 
-The public page of users, located at `/u/username` is visible if either:
+The public page of a user, located at `/u/username`, is always visible whether
+you are logged in or not.
+
+When visiting the public page of a user, you can only see the projects which
+you are privileged to.
 
-- You are logged in.
-- You are logged out, and the target user is authorized to (is Guest, Reporter, etc.) at least one public project.
+## Visibility of groups
 
-Otherwise, you will be redirected to the sign in page.
+The public page of a group, located at `/groups/groupname`, is always visible
+to everyone.
 
-When visiting the public page of an user, you will only see listed projects which you can view yourself.
+Logged out users will be able to see the description and the avatar of the
+group as well as all public projects belonging to that group.
 
 ## Restricting the use of public or internal projects
 
-In the Admin area under Settings you can disable public projects or public and internal projects for the entire GitLab installation to prevent people making code public by accident. The restricted visibility settings do not apply to admin users.
+In the Admin area under **Settings** (`/admin/application_settings`), you can
+restrict the use of visibility levels for users when they create a project or a
+snippet. This is useful to prevent people exposing their repositories to public
+by accident. The restricted visibility settings do not apply to admin users.
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index a8dc5c24df265454ac52a935df444c6d41af6631..cc8a22cd003269b388c25adfe007ff1f8ab2a3d8 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -1,10 +1,11 @@
 # Rake tasks
 
 - [Backup restore](backup_restore.md)
+- [Check](check.md)
 - [Cleanup](cleanup.md)
 - [Features](features.md)
 - [Maintenance](maintenance.md) and self-checks
 - [User management](user_management.md)
 - [Web hooks](web_hooks.md)
 - [Import](import.md) of git repositories in bulk
-- [Rebuild authorized_keys file](http://doc.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators
\ No newline at end of file
+- [Rebuild authorized_keys file](http://doc.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 4e645b21a85a3619c2227f506226901fb4dfdf05..093450a6de3dc3a5cc32bb5dc9edf43c047978b5 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -274,9 +274,6 @@ sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186
 # Start GitLab
 sudo gitlab-ctl start
 
-# Create satellites
-sudo gitlab-rake gitlab:satellites:create
-
 # Check GitLab
 sudo gitlab-rake gitlab:check SANITIZE=true
 ```
@@ -360,8 +357,8 @@ If you are using backup restore procedures you might encounter the following war
 
 ```
 psql:/var/opt/gitlab/backups/db/database.sql:22: ERROR:  must be owner of extension plpgsql
-psql:/var/opt/gitlab/backups/db/database.sql:2931: WARNING:  no privileges could be revoked for "public" (two occurences)
-psql:/var/opt/gitlab/backups/db/database.sql:2933: WARNING:  no privileges were granted for "public" (two occurences)
+psql:/var/opt/gitlab/backups/db/database.sql:2931: WARNING:  no privileges could be revoked for "public" (two occurrences)
+psql:/var/opt/gitlab/backups/db/database.sql:2933: WARNING:  no privileges were granted for "public" (two occurrences)
 
 ```
 
diff --git a/doc/raketasks/check.md b/doc/raketasks/check.md
new file mode 100644
index 0000000000000000000000000000000000000000..3ff3fee6a40335d256a810d30054a67df6e50afd
--- /dev/null
+++ b/doc/raketasks/check.md
@@ -0,0 +1,63 @@
+# Check Rake Tasks
+
+## Repository Integrity
+
+Even though Git is very resilient and tries to prevent data integrity issues,
+there are times when things go wrong. The following Rake tasks intend to
+help GitLab administrators diagnose problem repositories so they can be fixed.
+
+There are 3 things that are checked to determine integrity.
+
+1. Git repository file system check ([git fsck](https://git-scm.com/docs/git-fsck)).
+   This step verifies the connectivity and validity of objects in the repository.
+1. Check for `config.lock` in the repository directory.
+1. Check for any branch/references lock files in `refs/heads`.
+
+It's important to note that the existence of `config.lock` or reference locks
+alone do not necessarily indicate a problem. Lock files are routinely created
+and removed as Git and GitLab perform operations on the repository. They serve
+to prevent data integrity issues. However, if a Git operation is interrupted these
+locks may not be cleaned up properly.
+
+The following symptoms may indicate a problem with repository integrity. If users
+experience these symptoms you may use the rake tasks described below to determine
+exactly which repositories are causing the trouble.
+
+- Receiving an error when trying to push code - `remote: error: cannot lock ref`
+- A 500 error when viewing the GitLab dashboard or when accessing a specific project.
+
+### Check all GitLab repositories
+
+This task loops through all repositories on the GitLab server and runs the
+3 integrity checks described previously.
+
+```
+# omnibus-gitlab
+sudo gitlab-rake gitlab:repo:check
+
+# installation from source
+bundle exec rake gitlab:repo:check RAILS_ENV=production
+```
+
+### Check repositories for a specific user
+
+This task checks all repositories that a specific user has access to. This is important
+because sometimes you know which user is experiencing trouble but you don't know
+which project might be the cause.
+
+If the rake task is executed without brackets at the end, you will be prompted
+to enter a username.
+
+```bash
+# omnibus-gitlab
+sudo gitlab-rake gitlab:user:check_repos
+sudo gitlab-rake gitlab:user:check_repos[<username>]
+
+# installation from source
+bundle exec rake gitlab:user:check_repos RAILS_ENV=production
+bundle exec rake gitlab:user:check_repos[<username>] RAILS_ENV=production
+```
+
+Example output:
+
+![gitlab:user:check_repos output](check_repos_output.png)
diff --git a/doc/raketasks/check_repos_output.png b/doc/raketasks/check_repos_output.png
new file mode 100644
index 0000000000000000000000000000000000000000..916b16851018bd6de0258f465e6e125ea56d1e0c
Binary files /dev/null and b/doc/raketasks/check_repos_output.png differ
diff --git a/doc/raketasks/list_repos.md b/doc/raketasks/list_repos.md
new file mode 100644
index 0000000000000000000000000000000000000000..476428eb4f5db91c6bfb697669f920082f75572b
--- /dev/null
+++ b/doc/raketasks/list_repos.md
@@ -0,0 +1,30 @@
+# Listing repository directories
+
+You can print a list of all Git repositories on disk managed by
+GitLab with the following command:
+
+```
+# Omnibus
+sudo gitlab-rake gitlab:list_repos
+
+# Source
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:list_repos RAILS_ENV=production
+```
+
+If you only want to list projects with recent activity you can pass
+a date with the 'SINCE' environment variable.  The time you specify
+is parsed by the Rails [TimeZone#parse
+function](http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html#method-i-parse).
+
+```
+# Omnibus
+sudo gitlab-rake gitlab:list_repos SINCE='Sep 1 2015'
+
+# Source
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:list_repos RAILS_ENV=production SINCE='Sep 1 2015'
+```
+
+Note that the projects listed are NOT sorted by activity; they use
+the default ordering of the GitLab Rails application.
diff --git a/doc/release/README.md b/doc/release/README.md
index 1342b90f3b3c49888398bef85726fbb24cd31f57..52eca7c02a61e7c2043893f4fc3115f65c52e85d 100644
--- a/doc/release/README.md
+++ b/doc/release/README.md
@@ -1,4 +1,8 @@
-GitLab has the following updates:
+## Release cycle
+
+Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases are published when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG).  Features that will likely be in the next releases can be found on the [direction page](https://about.gitlab.com/direction/).
+
+## Release process documentation
 
 - [Monthly release](monthly.md), every month on the 22nd.
 - [Patch release](patch.md), if there are serious regressions.
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index aff3f066b247dacd42c09cdd2a71b02e3ac524f9..907c19e65a0300d795367c9e8409564a5fdc4c45 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -176,7 +176,7 @@ Tweet about the RC release:
 1. Also check the CI changelog
 1. Add a proposed tweet text to the blog post WIP MR description.
 1. Create a WIP MR for the blog post
-1. Make sure merge request title starts with `WIP` so it can not be accidently merged until ready.
+1. Make sure merge request title starts with `WIP` so it can not be accidentally merged until ready.
 1. Ask Dmitriy (or a team member with OS X) to add screenshots to the WIP MR.
 1. Decide with core team who will be the MVP user.
 1. Create WIP MR for adding MVP to MVP page on website
diff --git a/doc/security/README.md b/doc/security/README.md
index 473f3632dcdf852f83ae7feb52fb879c6c23024f..f34c792d00549596d2860c18f2985d84bca37090 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -4,4 +4,7 @@
 - [Rack attack](rack_attack.md)
 - [Web Hooks and insecure internal web services](webhooks.md)
 - [Information exclusivity](information_exclusivity.md)
-- [Reset your root password](reset_root_password.md)
\ No newline at end of file
+- [Reset your root password](reset_root_password.md)
+- [User File Uploads](user_file_uploads.md)
+- [How we manage the CRIME vulnerability](crime_vulnerability.md)
+- [Enforce Two-Factor authentication](two_factor_authentication.md)
diff --git a/doc/security/crime_vulnerability.md b/doc/security/crime_vulnerability.md
new file mode 100644
index 0000000000000000000000000000000000000000..94ba5d1375dd550178deecce20d36851054119a5
--- /dev/null
+++ b/doc/security/crime_vulnerability.md
@@ -0,0 +1,63 @@
+# How we manage the TLS protocol CRIME vulnerability
+
+> CRIME ("Compression Ratio Info-leak Made Easy") is a security exploit against
+secret web cookies over connections using the HTTPS and SPDY protocols that also
+use data compression. When used to recover the content of secret
+authentication cookies, it allows an attacker to perform session hijacking on an
+authenticated web session, allowing the launching of further attacks.
+([CRIME](https://en.wikipedia.org/w/index.php?title=CRIME&oldid=692423806))
+
+### Description
+
+The TLS Protocol CRIME Vulnerability affects compression over HTTPS, therefore
+it warns against using SSL Compression (for example gzip) or SPDY which
+optionally uses compression as well.
+
+GitLab supports both gzip and [SPDY][ngx-spdy] and mitigates the CRIME
+vulnerability by deactivating gzip when HTTPS is enabled. You can see the
+sources of the files in question:
+
+* [Source installation NGINX file][source-nginx]
+* [Omnibus installation NGINX file][omnibus-nginx]
+
+Although SPDY is enabled in Omnibus installations, CRIME relies on compression
+(the 'C') and the default compression level in NGINX's SPDY module is 0
+(no compression).
+
+### Nessus
+
+The Nessus scanner, [reports a possible CRIME vulnerability][nessus] in GitLab
+similar to the following format:
+
+```
+Description
+
+This remote service has one of two configurations that are known to be required for the CRIME attack:
+SSL/TLS compression is enabled.
+TLS advertises the SPDY protocol earlier than version 4.
+
+...
+
+Output
+
+The following configuration indicates that the remote service may be vulnerable to the CRIME attack:
+SPDY support earlier than version 4 is advertised.
+```
+
+From the report above it is important to note that Nessus is only checking if
+TLS advertises the SPDY protocol earlier than version 4, it does not perform an
+attack nor does it check if compression is enabled. With just this approach, it
+cannot tell that SPDY's compression is disabled and not subject to the CRIME
+vulnerability.
+
+### References
+
+* Nginx ["Module ngx_http_spdy_module"][ngx-spdy]
+* Tenable Network Security, Inc. ["Transport Layer Security (TLS) Protocol CRIME Vulnerability"][nessus]
+* Wikipedia contributors, ["CRIME"][wiki-crime] Wikipedia, The Free Encyclopedia
+
+[source-nginx]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/nginx/gitlab-ssl
+[omnibus-nginx]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/nginx-gitlab-http.conf.erb
+[ngx-spdy]: http://nginx.org/en/docs/http/ngx_http_spdy_module.html
+[nessus]: https://www.tenable.com/plugins/index.php?view=single&id=62565
+[wiki-crime]: https://en.wikipedia.org/wiki/CRIME
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
new file mode 100644
index 0000000000000000000000000000000000000000..4e25a1fdc3ff381c271726b968da2e01d0b97f79
--- /dev/null
+++ b/doc/security/two_factor_authentication.md
@@ -0,0 +1,38 @@
+# Enforce Two-factor Authentication (2FA)
+
+Two-factor Authentication (2FA) provides an additional level of security to your
+users' GitLab account. Once enabled, in addition to supplying their username and
+password to login, they'll be prompted for a code generated by an application on
+their phone.
+
+You can read more about it here:
+[Two-factor Authentication (2FA)](doc/profile/two_factor_authentication.md)
+
+## Enabling 2FA
+
+Users on GitLab, can enable it without any admin's intervention. If you want to
+enforce everyone to setup 2FA, you can choose from two different ways:
+
+ 1. Enforce on next login
+ 2. Suggest on next login, but allow a grace period before enforcing.
+
+In the Admin area under **Settings** (`/admin/application_settings`), look for
+the "Sign-in Restrictions" area, where you can configure both.
+
+If you want 2FA enforcement to take effect on next login, change the grace
+period to `0`
+
+## Disabling 2FA for everyone
+
+There may be some special situations where you want to disable 2FA for everyone
+even when forced 2FA is disabled. There is a rake task for that:
+
+```
+# use this command if you've installed GitLab with the Omnibus package
+sudo gitlab-rake gitlab:two_factor:disable_for_all_users
+
+# if you've installed GitLab from source
+sudo -u git -H bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production
+```
+
+**IMPORTANT: this is a permanent and irreversible action. Users will have to reactivate 2FA from scratch if they want to use it again.**
diff --git a/doc/security/user_file_uploads.md b/doc/security/user_file_uploads.md
new file mode 100644
index 0000000000000000000000000000000000000000..98493d33b001ac90f4f5fee4a5b0f85035efad8d
--- /dev/null
+++ b/doc/security/user_file_uploads.md
@@ -0,0 +1,11 @@
+# User File Uploads
+
+Images attached to issues, merge requests or comments do not require authentication
+to be viewed if someone knows the direct URL. This direct URL contains a random
+32-character ID that prevents unauthorized people from guessing the URL to an
+image containing sensitive information. We don't enable authentication because
+these images need to be visible in the body of notification emails, which are
+often read from email clients that are not authenticated with GitLab, like
+Outlook, Apple Mail, or the Mail app on your mobile device.
+
+Note that non-image attachments do require authentication to be viewed.
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 9753504ac8be72b46121aeb010518e073191e46e..fe5b45dd432edefef3acdd4dda0402ebfb7c8088 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -15,7 +15,8 @@ Note: It is a best practice to use a password for an SSH key, but it is not
 required and you can skip creating a password by pressing enter. Note that
 the password you choose here can't be altered or retrieved.
 
-To generate a new SSH key, use the following commandGitLab```bash
+To generate a new SSH key, use the following command:
+```bash
 ssh-keygen -t rsa -C "$your_email"
 ```
 This command will prompt you for a location and filename to store the key
@@ -108,4 +109,4 @@ Note in the gitlab.com example above a username was specified to override the de
 Due to the wide variety of SSH clients and their very large number of configuration options, further explanation of these topics is beyond the scope of this document.
 
 Public SSH keys need to be unique, as they will bind to your account. Your SSH key is the only identifier you'll
-have when pushing code via SSH. That's why it needs to uniquely map to a single user.
+have when pushing code via SSH. That's why it needs to uniquely map to a single user.
\ No newline at end of file
diff --git a/doc/update/8.1-to-8.2.md b/doc/update/8.1-to-8.2.md
index b57e999cfb7da1d17726124142fbee6625d193d6..46dfa2232b44e776c75927354b3a435b07654efd 100644
--- a/doc/update/8.1-to-8.2.md
+++ b/doc/update/8.1-to-8.2.md
@@ -68,7 +68,7 @@ sudo -u git -H git checkout 8-2-stable-ee
 ```bash
 cd /home/git/gitlab-shell
 sudo -u git -H git fetch
-sudo -u git -H git checkout v2.6.7
+sudo -u git -H git checkout v2.6.8
 ```
 
 ### 5. Replace gitlab-git-http-server with gitlab-workhorse
@@ -85,11 +85,10 @@ sudo -u git -H git checkout 0.4.2
 sudo -u git -H make
 ```
 
-Update the GitLab init script and 'default' file.
+Update the GitLab 'default' file.
 
 ```
 cd /home/git/gitlab
-sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
 test -e /etc/default/gitlab && \
   sudo sed -i.pre-8.2 's/^\([^=]*\)gitlab_git_http_server/\1gitlab_workhorse/' /etc/default/gitlab
 ```
@@ -143,7 +142,7 @@ git diff origin/8-1-stable:lib/support/nginx/gitlab origin/8-2-stable:lib/suppor
 
 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-git-http-server listen on a TCP port. You can do this
+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
diff --git a/doc/update/8.2-to-8.3.md b/doc/update/8.2-to-8.3.md
new file mode 100644
index 0000000000000000000000000000000000000000..3748941b7815a93c9de94fe1ace36496e1d01b29
--- /dev/null
+++ b/doc/update/8.2-to-8.3.md
@@ -0,0 +1,200 @@
+# From 8.2 to 8.3
+
+**NOTE:** GitLab 8.0 introduced several significant changes related to
+installation and configuration which *are not duplicated here*. Be sure you're
+already running a working version of at least 8.0 before proceeding with this
+guide.
+
+### 0. Double-check your Git version
+
+**This notice applies only to /usr/local/bin/git**
+
+If you compiled Git from source on your GitLab server then please double-check
+that you are using a version that protects against CVE-2014-9390. For six
+months after this vulnerability became known the GitLab installation guide
+still contained instructions that would install the outdated, 'vulnerable' Git
+version 2.1.2.
+
+Run the following command to get your current Git version:
+
+```sh
+/usr/local/bin/git --version
+```
+
+If you see 'No such file or directory' then you did not install Git according
+to the outdated instructions from the GitLab installation guide and you can go
+to the next step 'Stop server' below.
+
+If you see a version string then it should be v1.8.5.6, v1.9.5, v2.0.5, v2.1.4,
+v2.2.1 or newer. You can use the [instructions in the GitLab source installation
+guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
+to install a newer version of Git.
+
+### 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-3-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-3-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v2.6.9
+```
+
+### 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 0.5.1
+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
+
+# 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-2-stable:config/gitlab.yml.example origin/8-3-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+GitLab 8.3 introduces major changes in the NGINX configuration.
+Because all HTTP requests pass through gitlab-workhorse now a lot of
+directives need to be removed from NGINX. During future upgrades there
+should be much less changes in the NGINX configuration because of
+this.
+
+View changes between the previous recommended Nginx configuration and the
+current one:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-2-stable:lib/support/nginx/gitlab-ssl origin/8-3-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-2-stable:lib/support/nginx/gitlab origin/8-3-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-3-stable/lib/support/init.d/gitlab.default.example#L34
+
+#### Init script
+
+We updated the init script for GitLab in order to pass new
+configuration options to gitlab-workhorse. We let gitlab-workhorse
+connect to the Rails application via a Unix domain socket and we tell
+it where the 'public' directory of GitLab is.
+
+```
+cd /home/git/gitlab
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 8. Use Redis v2.8.0+
+
+Previous versions of GitLab allowed Redis versions >= 2.0 to be used, but
+GitLab 8.3 uses Sidekiq 4.0, which requires Redis 2.8. You can check your Redis version
+with the following command:
+
+    redis-cli info | grep redis_version
+
+If you need to upgrade, see the [installation guide for Redis](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-3-stable/doc/install/installation.md#6-redis).
+
+### 9. Start application
+
+    sudo service gitlab start
+    sudo service nginx restart
+
+### 10. 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.2)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 8.1 to 8.2](8.1-to-8.2.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.
+
+## Troubleshooting
+
+### "You appear to have cloned an empty repository."
+
+See the [7.14 to 8.0 update guide](7.14-to-8.0.md#troubleshooting).
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 593722eb01ff43e7af394446ca7f52827441742c..c19ee49f9e035bdcdfff57a3b913d3570c6b6d93 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -6,7 +6,8 @@ For example from 7.14.0 to 7.14.3, also see the [semantic versioning specificati
 ### 0. Backup
 
 It's useful to make a backup just in case things go south:
-(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
+(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab
+user on the database version)
 
 ```bash
 cd /home/git/gitlab
@@ -15,19 +16,23 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
 
 ### 1. Stop server
 
-    sudo service gitlab stop
+```bash
+sudo service gitlab stop
+```
 
 ### 2. Get latest code for the stable branch
 
+In the commands below, replace `LATEST_TAG` with the latest GitLab tag you want
+to update to, for example `v8.0.3`. Use `git tag -l 'v*.[0-9]' --sort='v:refname'`
+to see a list of all tags. Make sure to update patch versions only (check your
+current version with `cat VERSION`).
+
 ```bash
 cd /home/git/gitlab
 sudo -u git -H git fetch --all
 sudo -u git -H git checkout -- Gemfile.lock db/schema.rb
 sudo -u git -H git checkout LATEST_TAG -b LATEST_TAG
 ```
-Replace `LATEST_TAG` with the latest GitLab tag you want to update to, for example `v8.0.3`.  
-Use `git tag -l 'v*.[0-9]' --sort='v:refname'` to see a list of all tags.  
-Make sure to update patch versions only (check your current version with `cat VERSION`)
 
 ### 3. Update gitlab-shell to the corresponding version
 
@@ -37,12 +42,20 @@ sudo -u git -H git fetch
 sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` -b v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
 ```
 
-### 4. Install libs, migrations, etc.
+### 4. Update gitlab-workhorse to the corresponding version
+
+```bash
+cd /home/git/gitlab-workhorse
+sudo -u git -H git fetch
+sudo -u git -H git checkout `cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` -b `cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION`
+```
+
+### 5. Install libs, migrations, etc.
 
 ```bash
 cd /home/git/gitlab
 
-#PostgreSQL
+# PostgreSQL
 sudo -u git -H bundle install --without development test mysql --deployment
 
 # MySQL
@@ -52,19 +65,25 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
 sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
 ```
 
-### 5. Start application
+### 6. Start application
 
-    sudo service gitlab start
-    sudo service nginx restart
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
 
-### 6. Check application status
+### 7. 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
+```bash
+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 with:
 
-    sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```bash
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
 
 If all items are green, then congratulations upgrade complete!
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 7d838187a265fd7b2889e76409105ea70440ae66..6420d65cf1bcb0eed818a490f6dacb892bd0c290 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -57,6 +57,9 @@ X-Gitlab-Event: Push Hook
         "name": "Jordi Mallach",
         "email": "jordi@softcatala.org"
       }
+      "added": ["CHANGELOG"],
+      "modified": ["app/controller/application.rb"],
+      "removed": []
     },
     {
       "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
@@ -66,13 +69,14 @@ X-Gitlab-Event: Push Hook
       "author": {
         "name": "GitLab dev user",
         "email": "gitlabdev@dv6700.(none)"
-      }
+      },
+      "added": ["CHANGELOG"],
+      "modified": ["app/controller/application.rb"],
+      "removed": []
     }
   ],
-  "total_commits_count": 4,
-  "added": ["CHANGELOG"],
-  "modified": ["app/controller/application.rb"],
-  "removed": []
+  "total_commits_count": 4
+  
 }
 ```
 
@@ -184,7 +188,7 @@ X-Gitlab-Event: Note Hook
 {
   "object_kind": "note",
   "user": {
-    "name": "Adminstrator",
+    "name": "Administrator",
     "username": "root",
     "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
   },
@@ -337,7 +341,7 @@ X-Gitlab-Event: Note Hook
 {
   "object_kind": "note",
   "user": {
-    "name": "Adminstrator",
+    "name": "Administrator",
     "username": "root",
     "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
   },
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index a6b4d9511884f8c1f1a9cb21389bbd0cf7e91a62..3651b55f438ee3c9d45e56e6daf69dd5e8c6a3b3 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -17,4 +17,6 @@
 - [Milestones](milestones.md)
 - [Merge Requests](merge_requests.md)
 - ["Work In Progress" Merge Requests](wip_merge_requests.md)
+- [Merge When Build Succeeds](merge_when_build_succeeds.md)
 - [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md)
+- [Importing from SVN, GitHub, BitBucket, etc](importing/README.md)
diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md
index 7ccf06fbd60b8ef634dc98ce93c1537f9af44e76..18e5d950866a4c16f70a5155848bce9b74f0f80d 100644
--- a/doc/workflow/importing/README.md
+++ b/doc/workflow/importing/README.md
@@ -1,13 +1,17 @@
 # Migrating projects to a GitLab instance
 
 1. [Bitbucket](import_projects_from_bitbucket.md)
-2. [GitHub](import_projects_from_github.md)
-3. [GitLab.com](import_projects_from_gitlab_com.md)
-4. [FogBugz](import_projects_from_fogbugz.md)
-4. [SVN](migrating_from_svn.md)
+1. [GitHub](import_projects_from_github.md)
+1. [GitLab.com](import_projects_from_gitlab_com.md)
+1. [FogBugz](import_projects_from_fogbugz.md)
+1. [SVN](migrating_from_svn.md)
 
-### Note
-* If you'd like to migrate from a self-hosted GitLab instance to GitLab.com, you can copy your repos by changing the remote and pushing to the new server; but issues and merge requests can't be imported.
+In addition to the specific migration documentation above, you can import any
+Git repository via HTTP from the New Project page. Be aware that if the
+repository is too large the import can timeout.
+
+### Migrating from self-hosted GitLab to GitLab.com
+
+You can copy your repos by changing the remote and pushing to the new server;
+but issues and merge requests can't be imported.
 
-* You can import any Git repository via HTTP from the New Project page.
-If the repository is too large, it can timeout. 
diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md
index 1938ccd0c264c42b2d42b7772f85980e1d1d262d..b355a91b5a6e693ba060a5b49beca7ae0c9c635d 100644
--- a/doc/workflow/importing/migrating_from_svn.md
+++ b/doc/workflow/importing/migrating_from_svn.md
@@ -1,17 +1,78 @@
 # Migrating from SVN to GitLab
 
-SVN stands for Subversion and is a version control system (VCS).
-Git is a distributed version control system.
+Subversion (SVN) is a central version control system (VCS) while
+Git is a distributed version control system. There are some major differences
+between the two, for more information consult your favorite search engine.
 
-There are some major differences between the two, for more information consult your favorite search engine.
+If you are currently using an SVN repository, you can migrate the repository
+to Git and GitLab. We recommend a hard cut over - run the migration command once
+and then have all developers start using the new GitLab repository immediately.
+Otherwise, it's hard to keep changing in sync in both directions. The conversion
+process should be run on a local workstation.
 
-Git has tools for migrating SVN repositories to git, namely `git svn`. You can read more about this at
-[git documentation pages](https://git-scm.com/book/en/Git-and-Other-Systems-Git-and-Subversion).
+Install `svn2git`. On all systems you can install as a Ruby gem if you already
+have Ruby and Git installed.
 
-Apart from the [official git documentation](https://git-scm.com/book/en/Git-and-Other-Systems-Migrating-to-Git) there is also
-user created step by step guide for migrating from SVN to GitLab.
+```bash
+sudo gem install svn2git
+```
 
-[Benjamin New](https://github.com/leftclickben) wrote [a guide that shows how to do a migration](https://gist.github.com/leftclickben/322b7a3042cbe97ed2af). Mirrors can be found [here](https://gitlab.com/snippets/2168) and [here](https://gist.github.com/maxlazio/f1b593b0d00aa966e9ca).
+On Debian-based Linux distributions you can install the native packages:
+
+```bash
+sudo apt-get install git-core git-svn ruby
+```
+
+Optionally, prepare an authors file so `svn2git` can map SVN authors to Git authors.
+If you choose not to create the authors file then commits will not be attributed
+to the correct GitLab user. Some users may not consider this a big issue while
+others will want to ensure they complete this step. If you choose to map authors
+you will be required to map every author that is present on changes in the SVN
+repository. If you don't, the conversion will fail and you will have to update
+the author file accordingly. The following command will search through the
+repository and output a list of authors.
+
+```bash
+svn log --quiet | grep -E "r[0-9]+ \| .+ \|" | cut -d'|' -f2 | sed 's/ //g' | sort | uniq
+```
+
+Use the output from the last command to construct the authors file.
+Create a file called `authors.txt` and add one mapping per line.
+
+```
+janedoe = Jane Doe <janedoe@example.com>
+johndoe = John Doe <johndoe@example.com>
+```
+
+If your SVN repository is in the standard format (trunk, branches, tags,
+not nested) the conversion is simple. For a non-standard repository see
+[svn2git documentation](https://github.com/nirvdrum/svn2git). The following
+command will checkout the repository and do the conversion in the current
+working directory. Be sure to create a new directory for each repository before
+running the `svn2git` command. The conversion process will take some time.
+
+```bash
+svn2git https://svn.example.com/path/to/repo --authors /path/to/authors.txt
+```
+
+If your SVN repository requires a username and password add the
+`--username <username>` and `--password <password` flags to the above command.
+`svn2git` also supports excluding certain file paths, branches, tags, etc. See
+[svn2git documentation](https://github.com/nirvdrum/svn2git) or run
+`svn2git --help` for full documentation on all of the available options.
+
+Create a new GitLab project, where you will eventually push your converted code.
+Copy the SSH or HTTP(S) repository URL from the project page. Add the GitLab
+repository as a Git remote and push all the changes. This will push all commits,
+branches and tags.
+
+```bash
+git remote add origin git@gitlab.com:<group>/<project>.git
+git push --all origin
+```
 
 ## Contribute to this guide
-We welcome all contributions that would expand this guide with instructions on how to migrate from SVN and other version control systems.
+We welcome all contributions that would expand this guide with instructions on
+how to migrate from SVN and other version control systems.
+
+
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index 210a8f71c3b1668eab6846845acd2992a4762cc3..b59e92cb31798cbe20963082300f17ed1d7c19cc 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -121,6 +121,6 @@ git config --global credential.helper 'cache --timeout=3600'
 
 This will remember the credentials for an hour after which Git operations will require re-authentication.
 
-If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, `wincred` is available.
+If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases).
 
-More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
+More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
\ No newline at end of file
diff --git a/doc/workflow/merge_when_build_succeeds.md b/doc/workflow/merge_when_build_succeeds.md
new file mode 100644
index 0000000000000000000000000000000000000000..75e1fdff2b2f9f72cfc388bdcbea9adbed9cbb7f
--- /dev/null
+++ b/doc/workflow/merge_when_build_succeeds.md
@@ -0,0 +1,15 @@
+# Merge When Build Succeeds
+
+When reviewing a merge request that looks ready to merge but still has one or more CI builds running, you can set it to be merged automatically when all builds succeed. This way, you don't have to wait for the builds to finish and remember to merge the request manually.
+
+![Enable](merge_when_build_succeeds/enable.png)
+
+When you hit the "Merge When Build Succeeds" button, the status of the merge request will be updated to represent the impending merge. If you cannot wait for the build to succeed and want to merge immediately, this option is available in the dropdown menu on the right of the main button.
+
+Both team developers and the author of the merge request have the option to cancel the automatic merge if they find a reason why it shouldn't be merged after all.
+
+![Status](merge_when_build_succeeds/status.png)
+
+When the build succeeds, the merge request will automatically be merged. When the build fails, the author gets a chance to retry any failed builds, or to push new commits to fix the failure.
+
+When the builds are retried and succeed on the second try, the merge request will automatically be merged after all. When the merge request is updated with new commits, the automatic merge is automatically canceled to allow the new changes to be reviewed.
diff --git a/doc/workflow/merge_when_build_succeeds/enable.png b/doc/workflow/merge_when_build_succeeds/enable.png
new file mode 100644
index 0000000000000000000000000000000000000000..633efa1246f997ba0070c5ca483c9ac4057dc20b
Binary files /dev/null and b/doc/workflow/merge_when_build_succeeds/enable.png differ
diff --git a/doc/workflow/merge_when_build_succeeds/status.png b/doc/workflow/merge_when_build_succeeds/status.png
new file mode 100644
index 0000000000000000000000000000000000000000..c856c7d14dc6ba2f0c89f3e6d7c0e3de39e0dcfe
Binary files /dev/null and b/doc/workflow/merge_when_build_succeeds/status.png differ
diff --git a/features/admin/groups.feature b/features/admin/groups.feature
index 973918086a3827db0ba74085e09b77b5d606390c..2edb3964f7000bcdaf1d1df814337b4a765701ff 100644
--- a/features/admin/groups.feature
+++ b/features/admin/groups.feature
@@ -33,3 +33,19 @@ Feature: Admin Groups
     When I visit admin group page
     When I select user "johndoe@gitlab.com" from user list as "Reporter"
     Then I should see "johndoe@gitlab.com" in team list in every project as "Reporter"
+
+  @javascript
+  Scenario: Signed in admin should be able to add himself to a group
+    Given "John Doe" is owner of group "Owned"
+    When I visit group "Owned" members page
+    When I select current user as "Developer"
+    Then I should see current user as "Developer"
+
+  @javascript
+  Scenario: Signed in admin should be able to remove himself from group
+    Given current user is developer of group "Owned"
+    When I visit group "Owned" members page
+    Then I should see current user as "Developer"
+    When I click on the "Remove User From Group" button for current user
+    When I visit group "Owned" members page
+    Then I should not see current user as "Developer"
diff --git a/features/admin/projects.feature b/features/admin/projects.feature
index f7cec04eb75e7db842077385584dbbb18a1bfb51..c5ee80136c81aa3bec25f393a164b072d5fcc4ea 100644
--- a/features/admin/projects.feature
+++ b/features/admin/projects.feature
@@ -27,3 +27,19 @@ Feature: Admin Projects
     And I visit admin project page
     When I transfer project to group 'Web'
     Then I should see project transfered
+
+  @javascript
+  Scenario: Signed in admin should be able to add himself to a project
+    Given "John Doe" owns private project "Enterprise"
+    When I visit project "Enterprise" members page
+    When I select current user as "Developer"
+    Then I should see current user as "Developer"
+
+  @javascript
+  Scenario: Signed in admin should be able to remove himself from a project
+    Given "John Doe" owns private project "Enterprise"
+    And current user is developer of project "Enterprise"
+    When I visit project "Enterprise" members page
+    Then I should see current user as "Developer"
+    When I click on the "Remove User From Project" button for current user
+    Then I should not see current user as "Developer"
diff --git a/features/explore/projects.feature b/features/explore/projects.feature
index 5d3870827f55d480fcead134df7147dfafe4d6d4..629859e960dbadff673e6634c090de5f92f9e673 100644
--- a/features/explore/projects.feature
+++ b/features/explore/projects.feature
@@ -31,8 +31,17 @@ Feature: Explore Projects
     Then I should see empty public project details
     And I should see empty public project details with http clone info
 
-  Scenario: I visit an empty public project page as user
+  Scenario: I visit an empty public project page as user with no ssh-keys
     Given I sign in as a user
+    And I have no ssh keys
+    And public empty project "Empty Public Project"
+    When I visit empty project page
+    Then I should see empty public project details
+    And I should see empty public project details with http clone info
+
+  Scenario: I visit an empty public project page as user with an ssh-key
+    Given I sign in as a user
+    And I have an ssh key
     And public empty project "Empty Public Project"
     When I visit empty project page
     Then I should see empty public project details
@@ -57,8 +66,16 @@ Feature: Explore Projects
     Then I should see project "Community" home page
     And I should see an http link to the repository
 
-  Scenario: I visit public project page as user
+  Scenario: I visit public project page as user with no ssh-keys
+    Given I sign in as a user
+    And I have no ssh keys
+    When I visit project "Community" page
+    Then I should see project "Community" home page
+    And I should see an http link to the repository
+
+  Scenario: I visit public project page as user with an ssh-key
     Given I sign in as a user
+    And I have an ssh key
     When I visit project "Community" page
     Then I should see project "Community" home page
     And I should see an ssh link to the repository
diff --git a/features/group/members.feature b/features/group/members.feature
new file mode 100644
index 0000000000000000000000000000000000000000..1f9514bac39ed994f3f50602fc40d279e30e042e
--- /dev/null
+++ b/features/group/members.feature
@@ -0,0 +1,105 @@
+Feature: Group Members
+  Background:
+    Given I sign in as "John Doe"
+    And "John Doe" is owner of group "Owned"
+    And "John Doe" is guest of group "Guest"
+
+  @javascript
+  Scenario: I should add user to group "Owned"
+    Given User "Mary Jane" exists
+    When I visit group "Owned" members page
+    And I select user "Mary Jane" from list with role "Reporter"
+    Then I should see user "Mary Jane" in team list
+
+  @javascript
+  Scenario: Add user to group
+    Given gitlab user "Mike"
+    When I visit group "Owned" members page
+    When I select "Mike" as "Reporter"
+    Then I should see "Mike" in team list as "Reporter"
+
+  @javascript
+  Scenario: Ignore add user to group when is already Owner
+    Given gitlab user "Mike"
+    When I visit group "Owned" members page
+    When I select "Mike" as "Reporter"
+    Then I should see "Mike" in team list as "Owner"
+
+  @javascript
+  Scenario: Invite user to group
+    When I visit group "Owned" members page
+    When I select "sjobs@apple.com" as "Reporter"
+    Then I should see "sjobs@apple.com" in team list as invited "Reporter"
+
+  @javascript
+  Scenario: Edit group member permissions
+    Given "Mary Jane" is guest of group "Owned"
+    And I visit group "Owned" members page
+    When I change the "Mary Jane" role to "Developer"
+    Then I should see "Mary Jane" as "Developer"
+
+  # Leave
+
+  @javascript
+  Scenario: Owner should be able to remove himself from group if he is not the last owner
+    Given "Mary Jane" is owner of group "Owned"
+    When I visit group "Owned" members page
+    Then I should see user "John Doe" in team list
+    Then I should see user "Mary Jane" in team list
+    When I click on the "Remove User From Group" button for "John Doe"
+    And I visit group "Owned" members page
+    Then I should not see user "John Doe" in team list
+    Then I should see user "Mary Jane" in team list
+
+  @javascript
+  Scenario: Owner should not be able to remove himself from group if he is the last owner
+    Given "Mary Jane" is guest of group "Owned"
+    When I visit group "Owned" members page
+    Then I should see user "John Doe" in team list
+    Then I should see user "Mary Jane" in team list
+    Then I should not see the "Remove User From Group" button for "John Doe"
+
+  @javascript
+  Scenario: Guest should be able to remove himself from group
+    Given "Mary Jane" is guest of group "Guest"
+    When I visit group "Guest" members page
+    Then I should see user "John Doe" in team list
+    Then I should see user "Mary Jane" in team list
+    When I click on the "Remove User From Group" button for "John Doe"
+    When I visit group "Guest" members page
+    Then I should not see user "John Doe" in team list
+    Then I should see user "Mary Jane" in team list
+
+  @javascript
+  Scenario: Guest should be able to remove himself from group even if he is the only user in the group
+    When I visit group "Guest" members page
+    Then I should see user "John Doe" in team list
+    When I click on the "Remove User From Group" button for "John Doe"
+    When I visit group "Guest" members page
+    Then I should not see user "John Doe" in team list
+
+  # Remove others
+
+  Scenario: Owner should be able to remove other users from group
+    Given "Mary Jane" is owner of group "Owned"
+    When I visit group "Owned" members page
+    Then I should see user "John Doe" in team list
+    Then I should see user "Mary Jane" in team list
+    When I click on the "Remove User From Group" button for "Mary Jane"
+    When I visit group "Owned" members page
+    Then I should see user "John Doe" in team list
+    Then I should not see user "Mary Jane" in team list
+
+  Scenario: Guest should not be able to remove other users from group
+    Given "Mary Jane" is guest of group "Guest"
+    When I visit group "Guest" members page
+    Then I should see user "John Doe" in team list
+    Then I should see user "Mary Jane" in team list
+    Then I should not see the "Remove User From Group" button for "Mary Jane"
+
+  Scenario: Search member by name
+    Given "Mary Jane" is guest of group "Guest"
+    And I visit group "Guest" members page
+    When I search for 'Mary' member
+    Then I should see user "Mary Jane" in team list
+    Then I should not see user "John Doe" in team list
diff --git a/features/group/milestones.feature b/features/group/milestones.feature
new file mode 100644
index 0000000000000000000000000000000000000000..62ea66a783cc16fda8ca60e9209fff781ce63999
--- /dev/null
+++ b/features/group/milestones.feature
@@ -0,0 +1,30 @@
+Feature: Group Milestones
+  Background:
+    Given I sign in as "John Doe"
+    And "John Doe" is owner of group "Owned"
+
+  Scenario: I should see group "Owned" milestone index page with no milestones
+    When I visit group "Owned" page
+    And I click on group milestones
+    Then I should see group milestones index page has no milestones
+
+  Scenario: I should see group "Owned" milestone index page with milestones
+    Given Group has projects with milestones
+    When I visit group "Owned" page
+    And I click on group milestones
+    Then I should see group milestones index page with milestones
+
+  Scenario: I should see group "Owned" milestone show page
+    Given Group has projects with milestones
+    When I visit group "Owned" page
+    And I click on group milestones
+    And I click on one group milestone
+    Then I should see group milestone with descriptions and expiry date
+    And I should see group milestone with all issues and MRs assigned to that milestone
+
+  Scenario: Create multiple milestones with one form
+    Given I visit group "Owned" milestones page
+    And I click new milestone button
+    And I fill milestone name
+    When I press create mileston button
+    Then milestone in each project should be created
diff --git a/features/groups.feature b/features/groups.feature
index abf3769a84446c7355cf924a30abe0b2549892a2..c803e9529803b493abdd2db4d983b5baf11864d5 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -2,7 +2,6 @@ Feature: Groups
   Background:
     Given I sign in as "John Doe"
     And "John Doe" is owner of group "Owned"
-    And "John Doe" is guest of group "Guest"
 
   Scenario: I should have back to group button
     When I visit group "Owned" page
@@ -24,13 +23,6 @@ Feature: Groups
     When I visit group "Owned" merge requests page
     Then I should see merge requests from group "Owned" assigned to me
 
-  @javascript
-  Scenario: I should add user to projects in group "Owned"
-    Given User "Mary Jane" exists
-    When I visit group "Owned" members page
-    And I select user "Mary Jane" from list with role "Reporter"
-    Then I should see user "Mary Jane" in team list
-
   Scenario: I should see edit group "Owned" page
     When I visit group "Owned" settings page
     And I change group "Owned" name to "new-name"
@@ -51,123 +43,6 @@ Feature: Groups
     Then I should not see group "Owned" avatar
     And I should not see the "Remove avatar" button
 
-  @javascript
-  Scenario: Add user to group
-    Given gitlab user "Mike"
-    When I visit group "Owned" members page
-    And I click link "Add members"
-    When I select "Mike" as "Reporter"
-    Then I should see "Mike" in team list as "Reporter"
-
-  @javascript
-  Scenario: Ignore add user to group when is already Owner
-    Given gitlab user "Mike"
-    When I visit group "Owned" members page
-    And I click link "Add members"
-    When I select "Mike" as "Reporter"
-    Then I should see "Mike" in team list as "Owner"
-
-  @javascript
-  Scenario: Invite user to group
-    When I visit group "Owned" members page
-    And I click link "Add members"
-    When I select "sjobs@apple.com" as "Reporter"
-    Then I should see "sjobs@apple.com" in team list as invited "Reporter"
-
-  # Leave
-
-  @javascript
-  Scenario: Owner should be able to remove himself from group if he is not the last owner
-    Given "Mary Jane" is owner of group "Owned"
-    When I visit group "Owned" members page
-    Then I should see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-    When I click on the "Remove User From Group" button for "John Doe"
-    And I visit group "Owned" members page
-    Then I should not see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-
-  @javascript
-  Scenario: Owner should not be able to remove himself from group if he is the last owner
-    Given "Mary Jane" is guest of group "Owned"
-    When I visit group "Owned" members page
-    Then I should see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-    Then I should not see the "Remove User From Group" button for "John Doe"
-
-  @javascript
-  Scenario: Guest should be able to remove himself from group
-    Given "Mary Jane" is guest of group "Guest"
-    When I visit group "Guest" members page
-    Then I should see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-    When I click on the "Remove User From Group" button for "John Doe"
-    When I visit group "Guest" members page
-    Then I should not see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-
-  @javascript
-  Scenario: Guest should be able to remove himself from group even if he is the only user in the group
-    When I visit group "Guest" members page
-    Then I should see user "John Doe" in team list
-    When I click on the "Remove User From Group" button for "John Doe"
-    When I visit group "Guest" members page
-    Then I should not see user "John Doe" in team list
-
-  # Remove others
-
-  Scenario: Owner should be able to remove other users from group
-    Given "Mary Jane" is owner of group "Owned"
-    When I visit group "Owned" members page
-    Then I should see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-    When I click on the "Remove User From Group" button for "Mary Jane"
-    When I visit group "Owned" members page
-    Then I should see user "John Doe" in team list
-    Then I should not see user "Mary Jane" in team list
-
-  Scenario: Guest should not be able to remove other users from group
-    Given "Mary Jane" is guest of group "Guest"
-    When I visit group "Guest" members page
-    Then I should see user "John Doe" in team list
-    Then I should see user "Mary Jane" in team list
-    Then I should not see the "Remove User From Group" button for "Mary Jane"
-
-  Scenario: Search member by name
-    Given "Mary Jane" is guest of group "Guest"
-    And I visit group "Guest" members page
-    When I search for 'Mary' member
-    Then I should see user "Mary Jane" in team list
-    Then I should not see user "John Doe" in team list
-
-  # Group milestones
-
-  Scenario: I should see group "Owned" milestone index page with no milestones
-    When I visit group "Owned" page
-    And I click on group milestones
-    Then I should see group milestones index page has no milestones
-
-  Scenario: I should see group "Owned" milestone index page with milestones
-    Given Group has projects with milestones
-    When I visit group "Owned" page
-    And I click on group milestones
-    Then I should see group milestones index page with milestones
-
-  Scenario: I should see group "Owned" milestone show page
-    Given Group has projects with milestones
-    When I visit group "Owned" page
-    And I click on group milestones
-    And I click on one group milestone
-    Then I should see group milestone with descriptions and expiry date
-    And I should see group milestone with all issues and MRs assigned to that milestone
-
-  Scenario: Create multiple milestones with one form
-    Given I visit group "Owned" milestones page
-    And I click new milestone button
-    And I fill milestone name
-    When I press create mileston button
-    Then milestone in each project should be created
-
   # Group projects in settings
   Scenario: I should see all projects in the project list in settings
     Given Group "Owned" has archived project
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index 8661ea98c20e96cea4c2aa6c021e105585bceda7..2fd097d100bc72dd331224241ed562ff122fd8f2 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -20,11 +20,6 @@ Feature: Project Active Tab
     Then the active main tab should be Commits
     And no other main tabs should be active
 
-  Scenario: On Project Network
-    Given I visit my project's network page
-    Then the active main tab should be Network
-    And no other main tabs should be active
-
   Scenario: On Project Issues
     Given I visit my project's issues page
     Then the active main tab should be Issues
@@ -83,6 +78,12 @@ Feature: Project Active Tab
     And no other sub tabs should be active
     And the active main tab should be Commits
 
+  Scenario: On Project Commits/Network
+    Given I visit my project's network page
+    Then the active sub tab should be Network
+    And no other sub tabs should be active
+    And the active main tab should be Commits
+
   Scenario: On Project Commits/Compare
     Given I visit my project's commits page
     And I click the "Compare" tab
diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature
index 65d8e48b9b3978c7b4d88a38ed0ecb571b859ca5..2c17d32154a9788bcb168213358aa305e506c154 100644
--- a/features/project/commits/branches.feature
+++ b/features/project/commits/branches.feature
@@ -1,3 +1,4 @@
+@project_commits
 Feature: Project Commits Branches
   Background:
     Given I sign in as a user
@@ -24,6 +25,7 @@ Feature: Project Commits Branches
     And I click branch 'improve/awesome' delete link
     Then I should not see branch 'improve/awesome'
 
+  @javascript
   Scenario: I create a branch with invalid name
     Given I visit project branches page
     And I click new branch link
diff --git a/features/project/commits/comments.feature b/features/project/commits/comments.feature
index 320f008abb63a42ce47289b5dfbdaee9c0740ed7..fafb54b183a219090db2b6816261ead641b9db7b 100644
--- a/features/project/commits/comments.feature
+++ b/features/project/commits/comments.feature
@@ -1,3 +1,4 @@
+@project_commits
 Feature: Project Commits Comments
   Background:
     Given I sign in as a user
diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature
index e4beeb59adc38a3ceb46c7db3d71d631e05090ff..5bb2d0e976bbdcb19510454773ada7c37847d890 100644
--- a/features/project/commits/commits.feature
+++ b/features/project/commits/commits.feature
@@ -1,3 +1,4 @@
+@project_commits
 Feature: Project Commits
   Background:
     Given I sign in as a user
@@ -18,7 +19,8 @@ Feature: Project Commits
 
   Scenario: I browse commit with ci from list
     Given commit has ci status
-    And I click on commit link
+    And repository contains ".gitlab-ci.yml" file
+    When I click on commit link
     Then I see commit ci info
     And I click status link
     Then I see builds list
diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature
index 4a2b870e0822987798d4c8b238d6122a5b9a1142..2bde4c8a99ba518fa59b39fbbfbb2038ac369335 100644
--- a/features/project/commits/diff_comments.feature
+++ b/features/project/commits/diff_comments.feature
@@ -1,3 +1,4 @@
+@project_commits
 Feature: Project Commits Diff Comments
   Background:
     Given I sign in as a user
@@ -13,6 +14,12 @@ Feature: Project Commits Diff Comments
     Given I leave a diff comment like "Typo, please fix"
     Then I should see a diff comment saying "Typo, please fix"
 
+  @javascript
+  Scenario: I can add a diff comment with a single emoji
+    Given I open a diff comment form
+    And I write a diff comment like ":smile:"
+    Then I should see a diff comment with an emoji image
+
   @javascript
   Scenario: I get a temporary form for the first comment on a diff line
     Given I open a diff comment form
diff --git a/features/project/commits/tags.feature b/features/project/commits/tags.feature
index 56ee091acc0399cf07220a17251b965014e652bb..a4be39b2d40899542487794da6c94682a154d203 100644
--- a/features/project/commits/tags.feature
+++ b/features/project/commits/tags.feature
@@ -1,3 +1,4 @@
+@project_commits
 Feature: Project Commits Tags
   Background:
     Given I sign in as a user
diff --git a/features/project/commits/user_lookup.feature b/features/project/commits/user_lookup.feature
index db51d4a6cfa94f7afc72b268be3931349fd4497e..c18f4e070f3ecfad53edf117a505b501c4cb58d0 100644
--- a/features/project/commits/user_lookup.feature
+++ b/features/project/commits/user_lookup.feature
@@ -1,3 +1,4 @@
+@project_commits
 Feature: Project Commits User Lookup
   Background:
     Given I sign in as a user
diff --git a/features/project/create.feature b/features/project/create.feature
index e9dc4fe6b3c4d83b06e2dd49f867904c541b2980..27136798e36d14f9b2b55a306f8b8bc30b189297 100644
--- a/features/project/create.feature
+++ b/features/project/create.feature
@@ -1,3 +1,4 @@
+@project-create
 Feature: Project Create
   In order to get access to project sections
   A user with ability to create a project
@@ -7,6 +8,7 @@ Feature: Project Create
   Scenario: User create a project
     Given I sign in as a user
     When I visit new project page
+    And I have an ssh key
     And fill project form with valid data
     Then I should see project page
     And I should see empty project instuctions
@@ -14,6 +16,7 @@ Feature: Project Create
   @javascript
   Scenario: Empty project instructions
     Given I sign in as a user
+    And I have an ssh key
     When I visit new project page
     And fill project form with valid data
     Then I see empty project instuctions
diff --git a/features/project/graph.feature b/features/project/graph.feature
index 2acd65aea5fc208e991de70690e0a189163ba043..63793d6f989190409987d206b25e7ccb66d679ab 100644
--- a/features/project/graph.feature
+++ b/features/project/graph.feature
@@ -18,3 +18,8 @@ Feature: Project Graph
     Given project "Shop" has CI enabled
     When I visit project "Shop" CI graph page
     Then page should have CI graphs
+
+  @javascript
+  Scenario: I should see project languages graphs
+    When I visit project "Shop" languages graph page
+    Then page should have languages graphs
diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature
index a9bc8ffb9bb85ff245295621c0b46937f0801628..9a06fdc2ee6eb5ea0a40a84ea441a31cc3b5ebdc 100644
--- a/features/project/issues/award_emoji.feature
+++ b/features/project/issues/award_emoji.feature
@@ -1,3 +1,4 @@
+@project_issues
 Feature: Award Emoji
   Background:
     Given I sign in as a user
@@ -11,4 +12,19 @@ Feature: Award Emoji
     And I click to emoji in the picker
     Then I have award added
     And I can remove it by clicking to icon
-    
\ No newline at end of file
+
+  @javascript
+  Scenario: I can see the list of emoji categories
+    Given I click to emoji-picker
+    Then I can see the activity and food categories
+
+  @javascript
+  Scenario: I can search emoji
+    Given I click to emoji-picker
+    And I search "hand"
+    Then I see search result for "hand"
+
+  @javascript
+  Scenario: I add award emoji using regular comment
+    Given I leave comment with a single emoji
+    Then I have award added
diff --git a/features/project/issues/filter_labels.feature b/features/project/issues/filter_labels.feature
index e316f519861ec86a4a622b60c6ec487a3f5acc8d..e07f8053fb7ddffaa80cd6faa6302d5151fa0695 100644
--- a/features/project/issues/filter_labels.feature
+++ b/features/project/issues/filter_labels.feature
@@ -1,3 +1,4 @@
+@project_issues
 Feature: Project Issues Filter Labels
   Background:
     Given I sign in as a user
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index 28cc43ef710110a152319be8b1be6c65959dd1ff..ab234bc7507dc3766aaa944e2d238c051f35ca89 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -1,3 +1,4 @@
+@project_issues
 Feature: Project Issues
   Background:
     Given I sign in as a user
@@ -197,3 +198,8 @@ Feature: Project Issues
     And I submit new issue "500 error on profile"
     Then I should see issue "500 error on profile"
 
+  @javascript
+  Scenario: Another user adds a comment to issue I'm currently viewing
+    Given I visit issue page "Release 0.4"
+    And another user adds a comment with text "Yay!" to issue "Release 0.4"
+    Then I should see a new comment with text "Yay!"
diff --git a/features/project/issues/labels.feature b/features/project/issues/labels.feature
index 039a7d83cb1926a36bed98b352f366b165ac0774..45de57f18e328e8a7880744b0c5c70f9cb62424c 100644
--- a/features/project/issues/labels.feature
+++ b/features/project/issues/labels.feature
@@ -1,3 +1,4 @@
+@project_issues
 Feature: Project Issues Labels
   Background:
     Given I sign in as a user
diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature
index c1a20e9b488c16bcba801a449d4ae82db1b08eb0..1af05b3c326cf547c229ae4f1bf249a259097f14 100644
--- a/features/project/issues/milestones.feature
+++ b/features/project/issues/milestones.feature
@@ -1,3 +1,4 @@
+@project_issues
 Feature: Project Issues Milestones
   Background:
     Given I sign in as a user
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 6cd081c868e6582c66be1a40de7a032c1def9b4b..aa9078b878f9d3c39d1b2650d071a501b684e1d8 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -1,3 +1,4 @@
+@project_merge_requests
 Feature: Project Merge Requests
   Background:
     Given I sign in as a user
@@ -83,6 +84,26 @@ Feature: Project Merge Requests
     And I switch to the merge request's comments tab
     Then I should see a discussion has started on diff
 
+  @javascript
+  Scenario: I edit a comment on a merge request diff
+    Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+    And I visit merge request page "Bug NS-05"
+    And I click on the Changes tab
+    And I leave a comment like "Line is wrong" on diff
+    And I change the comment "Line is wrong" to "Typo, please fix" on diff
+    Then I should not see a diff comment saying "Line is wrong"
+    And I should see a diff comment saying "Typo, please fix"
+
+  @javascript
+  Scenario: I delete a comment on a merge request diff
+    Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+    And I visit merge request page "Bug NS-05"
+    And I click on the Changes tab
+    And I leave a comment like "Line is wrong" on diff
+    And I delete the comment "Line is wrong" on diff
+    And I click on the Discussion tab
+    Then I should not see any discussion
+
   @javascript
   Scenario: I comment on a line of a commit in merge request
     Given project "Shop" have "Bug NS-05" open merge request with diffs inside
diff --git a/features/project/merge_requests/accept.feature b/features/project/merge_requests/accept.feature
new file mode 100644
index 0000000000000000000000000000000000000000..330ec8ae0fe552cc9b4480525724626135a584d8
--- /dev/null
+++ b/features/project/merge_requests/accept.feature
@@ -0,0 +1,28 @@
+@project_merge_requests
+Feature: Project Merge Requests Acceptance
+  Background:
+    Given There is an open Merge Request
+      And I am signed in as a developer of the project
+
+  @javascript
+  Scenario: Accepting the Merge Request and removing the source branch
+    Given I am on the Merge Request detail page
+    When I click on "Remove source branch" option
+    And I click on Accept Merge Request
+    Then I should see merge request merged
+    And I should not see the Remove Source Branch button
+
+  @javascript
+  Scenario: Accepting the Merge Request when URL has an anchor
+    Given I am on the Merge Request detail with note anchor page
+    When I click on "Remove source branch" option
+    And I click on Accept Merge Request
+    Then I should see merge request merged
+    And I should not see the Remove Source Branch button
+
+  @javascript
+  Scenario: Accepting the Merge Request without removing the source branch
+    Given I am on the Merge Request detail page
+    When I click on Accept Merge Request
+    Then I should see merge request merged
+    And I should see the Remove Source Branch button
diff --git a/features/project/service.feature b/features/project/service.feature
index 5014b52b9f67a65fbcedfdbd401dd8107741a1d2..3a7b830852489d2560619a4aabc6203023827cff 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -7,12 +7,6 @@ Feature: Project Services
     When I visit project "Shop" services page
     Then I should see list of available services
 
-  Scenario: Activate gitlab-ci service
-    When I visit project "Shop" services page
-    And I click gitlab-ci service link
-    And I fill gitlab-ci settings
-    Then I should see service settings saved
-
   Scenario: Activate hipchat service
     When I visit project "Shop" services page
     And I click hipchat service link
@@ -61,6 +55,12 @@ Feature: Project Services
     And I fill email on push settings
     Then I should see email on push service settings saved
 
+  Scenario: Activate JIRA service
+    When I visit project "Shop" services page
+    And I click jira service link
+    And I fill jira settings
+    Then I should see jira service settings saved
+
   Scenario: Activate Irker (IRC Gateway) service
     When I visit project "Shop" services page
     And I click Irker service link
diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature
index 0f71c32380bf2a831187e1f699474833175b981f..10e7c23461057db7f8a9f825fec983f8b75d0b74 100644
--- a/features/project/shortcuts.feature
+++ b/features/project/shortcuts.feature
@@ -19,7 +19,8 @@ Feature: Project Shortcuts
   @javascript
   Scenario: Navigate to network tab
     Given I press "g" and "n"
-    Then the active main tab should be Network
+    Then the active sub tab should be Network
+    And the active main tab should be Commits
 
   @javascript
   Scenario: Navigate to graphs tab
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index e545ea63ca880efe84b78f94826df221ad421702..a8c276b949ea51f9b928c72acdf6d5b4fd414e0b 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -24,6 +24,12 @@ Feature: Project Source Browse Files
     Given I click on "New file" link in repo
     Then I can see new file page
 
+  Scenario: I can create file when I don't have write access
+    Given I don't have write access
+    And I click on "New file" link in repo
+    Then I should see a notice about a new fork having been created
+    Then I can see new file page
+
   @javascript
   Scenario: I can create and commit file
     Given I click on "New file" link in repo
@@ -34,6 +40,39 @@ Feature: Project Source Browse Files
     Then I am redirected to the new file
     And I should see its new content
 
+  @javascript
+  Scenario: I can create and commit file when I don't have write access
+    Given I don't have write access
+    And I click on "New file" link in repo
+    And I edit code
+    And I fill the new file name
+    And I fill the commit message
+    And I click on "Commit Changes"
+    Then I am redirected to the fork's new merge request page
+    And I can see the new commit message
+
+  @javascript
+  Scenario: I can create and commit file with new lines at the end of file
+    Given I click on "New file" link in repo
+    And I edit code with new lines at end of file
+    And I fill the new file name
+    And I fill the commit message
+    And I click on "Commit Changes"
+    Then I am redirected to the new file
+    And I click button "Edit"
+    And I should see its content with new lines preserved at end of file
+
+  @javascript
+  Scenario: I can create and commit file and specify new branch
+    Given I click on "New file" link in repo
+    And I edit code
+    And I fill the new file name
+    And I fill the commit message
+    And I fill the new branch name
+    And I click on "Commit Changes"
+    Then I am redirected to the new merge request page
+    And I should see its new content
+
   @javascript
   Scenario: I can upload file and commit
     Given I click on "Upload file" link in repo
@@ -45,6 +84,19 @@ Feature: Project Source Browse Files
     And I am redirected to the new merge request page
     And I can see the new commit message
 
+  @javascript
+  Scenario: I can upload file and commit when I don't have write access
+    Given I don't have write access
+    And I click on "Upload file" link in repo
+    Then I should see a notice about a new fork having been created
+    When I click on "Upload file" link in repo
+    And I upload a new text file
+    And I fill the upload file commit message
+    And I click on "Upload file"
+    Then I can see the new text file
+    And I am redirected to the fork's new merge request page
+    And I can see the new commit message
+
   @javascript
   Scenario: I can replace file and commit
     Given I click on ".gitignore" file in repo
@@ -57,15 +109,19 @@ Feature: Project Source Browse Files
     And I can see the replacement commit message
 
   @javascript
-  Scenario: I can create and commit file and specify new branch
-    Given I click on "New file" link in repo
-    And I edit code
-    And I fill the new file name
-    And I fill the commit message
-    And I fill the new branch name
-    And I click on "Commit Changes"
-    Then I am redirected to the new merge request page
-    And I should see its new content
+  Scenario: I can replace file and commit when I don't have write access
+    Given I don't have write access
+    And I click on ".gitignore" file in repo
+    And I see the ".gitignore"
+    And I click on "Replace"
+    Then I should see a notice about a new fork having been created
+    When I click on "Replace"
+    And I replace it with a text file
+    And I fill the replace file commit message
+    And I click on "Replace file"
+    Then I can see the new text file
+    And I am redirected to the fork's new merge request page
+    And I can see the replacement commit message
 
   @javascript
   Scenario: I can create file in empty repo
@@ -106,16 +162,18 @@ Feature: Project Source Browse Files
     And I click button "Edit"
     Then I can edit code
 
+  @javascript
+  Scenario: I can edit file when I don't have write access
+    Given I don't have write access
+    And I click on ".gitignore" file in repo
+    And I click button "Edit"
+    Then I should see a notice about a new fork having been created
+    And I can edit code
+
   Scenario: If the file is binary the edit link is hidden
     Given I visit a binary file in the repo
     Then I cannot see the edit button
 
-  Scenario: If I don't have edit permission the edit link is disabled
-    Given public project "Community"
-    And I visit project "Community" source page
-    And I click on ".gitignore" file in repo
-    Then The edit button is disabled
-
   @javascript
   Scenario: I can edit and commit file
     Given I click on ".gitignore" file in repo
@@ -126,6 +184,17 @@ Feature: Project Source Browse Files
     Then I am redirected to the ".gitignore"
     And I should see its new content
 
+  @javascript
+  Scenario: I can edit and commit file when I don't have write access
+    Given I don't have write access
+    And I click on ".gitignore" file in repo
+    And I click button "Edit"
+    And I edit code
+    And I fill the commit message
+    And I click on "Commit Changes"
+    Then I am redirected to the fork's new merge request page
+    And I can see the new commit message
+
   @javascript
   Scenario: I can edit and commit file to new branch
     Given I click on ".gitignore" file in repo
@@ -156,6 +225,17 @@ Feature: Project Source Browse Files
     And I click on "Create directory"
     Then I am redirected to the new merge request page
 
+  @javascript
+  Scenario: I can create directory in repo when I don't have write access
+    Given I don't have write access
+    When I click on "New directory" link in repo
+    Then I should see a notice about a new fork having been created
+    When I click on "New directory" link in repo
+    And I fill the new directory name
+    And I fill the commit message
+    And I click on "Create directory"
+    Then I am redirected to the fork's new merge request page
+
   @javascript
   Scenario: I attempt to create an existing directory
     When I click on "New directory" link in repo
@@ -183,6 +263,19 @@ Feature: Project Source Browse Files
     Then I am redirected to the files URL
     And I don't see the ".gitignore"
 
+  @javascript
+  Scenario: I can delete file and commit when I don't have write access
+    Given I don't have write access
+    And I click on ".gitignore" file in repo
+    And I see the ".gitignore"
+    And I click on "Delete"
+    Then I should see a notice about a new fork having been created
+    When I click on "Delete"
+    And I fill the commit message
+    And I click on "Delete file"
+    Then I am redirected to the fork's new merge request page
+    And I can see the new commit message
+
   Scenario: I can browse directory with Browse Dir
     Given I click on files directory
     And I click on History link
@@ -221,3 +314,9 @@ Feature: Project Source Browse Files
     Given I switch ref to fix
     And I visit the fix tree
     Then I see the commit data for a directory with a leading dot
+
+  Scenario: I browse LFS object
+    Given I click on "files/lfs/lfs_object.iso" file in repo
+    Then I should see download link and object size
+    And I should not see lfs pointer details
+    And I should see buttons for allowed commands
diff --git a/features/project/star.feature b/features/project/star.feature
index a45f9c470ea8921eb2f023899752af0af6e83d38..618f44fe6dc31cc2a4ce916debbcca2a85cfc233 100644
--- a/features/project/star.feature
+++ b/features/project/star.feature
@@ -1,3 +1,4 @@
+@project-stars
 Feature: Project Star
   Scenario: New projects have 0 stars
     Given public project "Community"
diff --git a/features/project/team_management.feature b/features/project/team_management.feature
index 09a7df59df62df3f269448adf80750908846e939..06fb45c8bded626ecbe04dc756227bf4e1f3c7da 100644
--- a/features/project/team_management.feature
+++ b/features/project/team_management.feature
@@ -13,14 +13,12 @@ Feature: Project Team Management
 
   @javascript
   Scenario: Add user to project
-    Given I click link "Add members"
-    And I select "Mike" as "Reporter"
+    When I select "Mike" as "Reporter"
     Then I should see "Mike" in team list as "Reporter"
 
   @javascript
   Scenario: Invite user to project
-    Given I click link "Add members"
-    And I select "sjobs@apple.com" as "Reporter"
+    When I select "sjobs@apple.com" as "Reporter"
     Then I should see "sjobs@apple.com" in team list as invited "Reporter"
 
   @javascript
diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb
index d27634858a2f2328379a97399aa14df770ae0ad5..43fd91d0d4cfedb5df8513e262e90019b41f628a 100644
--- a/features/steps/admin/groups.rb
+++ b/features/steps/admin/groups.rb
@@ -1,5 +1,6 @@
 class Spinach::Features::AdminGroups < Spinach::FeatureSteps
   include SharedAuthentication
+  include SharedGroup
   include SharedPaths
   include SharedUser
   include SharedActiveTab
@@ -88,6 +89,34 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
     end
   end
 
+  step 'I select current user as "Developer"' do
+    page.within ".users-group-form" do
+      select2(current_user.id, from: "#user_ids", multiple: true)
+      select "Developer", from: "access_level"
+    end
+
+    click_button "Add users to group"
+  end
+
+  step 'I should see current user as "Developer"' do
+    page.within '.content-list' do
+      expect(page).to have_content(current_user.name)
+      expect(page).to have_content('Developer')
+    end
+  end
+
+  step 'I click on the "Remove User From Group" button for current user' do
+    find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click
+    # poltergeist always confirms popups.
+  end
+
+  step 'I should not see current user as "Developer"' do
+    page.within '.content-list' do
+      expect(page).not_to have_content(current_user.name)
+      expect(page).not_to have_content('Developer')
+    end
+  end
+
   protected
 
   def current_group
diff --git a/features/steps/admin/labels.rb b/features/steps/admin/labels.rb
index b45d98658bc83ac9a8894c99d005d9c97845e5d7..55ddcc25085f3046d860ddf4587f8dc173b8664b 100644
--- a/features/steps/admin/labels.rb
+++ b/features/steps/admin/labels.rb
@@ -17,7 +17,7 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
 
   step 'I remove label \'bug\'' do
     page.within "#label_#{bug_label.id}" do
-      click_link 'Remove'
+      click_link 'Delete'
     end
   end
 
@@ -45,21 +45,21 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
   step 'I submit new label \'support\'' do
     visit new_admin_label_path
     fill_in 'Title', with: 'support'
-    fill_in 'Background Color', with: '#F95610'
+    fill_in 'Background color', with: '#F95610'
     click_button 'Save'
   end
 
   step 'I submit new label \'bug\'' do
     visit new_admin_label_path
     fill_in 'Title', with: 'bug'
-    fill_in 'Background Color', with: '#F95610'
+    fill_in 'Background color', with: '#F95610'
     click_button 'Save'
   end
 
   step 'I submit new label with invalid color' do
     visit new_admin_label_path
     fill_in 'Title', with: 'support'
-    fill_in 'Background Color', with: '#12'
+    fill_in 'Background color', with: '#12'
     click_button 'Save'
   end
 
@@ -71,7 +71,7 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
 
   step 'I should see label color error message' do
     page.within '.label-form' do
-      expect(page).to have_content 'Color is invalid'
+      expect(page).to have_content 'Color must be a valid color code'
     end
   end
 
@@ -101,7 +101,7 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
 
   step 'I change label \'bug\' to \'fix\'' do
     fill_in 'Title', with: 'fix'
-    fill_in 'Background Color', with: '#F15610'
+    fill_in 'Background color', with: '#F15610'
     click_button 'Save'
   end
 
diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb
index 5a1cc9aa15145c6f190efef66e92cd6d617f1112..a7a28755a6cfb8df5a655c42ea992efb16c6f160 100644
--- a/features/steps/admin/projects.rb
+++ b/features/steps/admin/projects.rb
@@ -3,6 +3,8 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
   include SharedPaths
   include SharedAdmin
   include SharedProject
+  include SharedUser
+  include Select2Helper
 
   step 'I should see all non-archived projects' do
     Project.non_archived.each do |p|
@@ -56,6 +58,41 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
     expect(page).to have_content 'Namespace: Web'
   end
 
+  step 'I visit project "Enterprise" members page' do
+    project = Project.find_by!(name: "Enterprise")
+    visit namespace_project_project_members_path(project.namespace, project)
+  end
+
+  step 'I select current user as "Developer"' do
+    page.within ".users-project-form" do
+      select2(current_user.id, from: "#user_ids", multiple: true)
+      select "Developer", from: "access_level"
+    end
+
+    click_button "Add users to project"
+  end
+
+  step 'I should see current user as "Developer"' do
+    page.within '.content-list' do
+      expect(page).to have_content(current_user.name)
+      expect(page).to have_content('Developer')
+    end
+  end
+
+  step 'current user is developer of project "Enterprise"' do
+    project = Project.find_by!(name: "Enterprise")
+    project.team << [current_user, :developer]
+  end
+
+  step 'I click on the "Remove User From Project" button for current user' do
+    find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click
+    # poltergeist always confirms popups.
+  end
+
+  step 'I should not see current_user as "Developer"' do
+    expect(page).not_to have_selector(:css, '.content-list')
+  end
+
   def project
     @project ||= Project.first
   end
diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb
index 6acbf46eb209dc7e633b0f1fd4c9656b4d508a81..037f7494a77295b9434de023ac948d79fc81a415 100644
--- a/features/steps/admin/settings.rb
+++ b/features/steps/admin/settings.rb
@@ -32,6 +32,7 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
     page.check('Comments')
     page.check('Issues events')
     page.check('Merge Request events')
+    page.check('Build events')
     click_on 'Save'
   end
 
@@ -39,6 +40,7 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
     fill_in 'Webhook', with: 'http://localhost'
     fill_in 'Username', with: 'test_user'
     fill_in 'Channel', with: '#test_channel'
+    page.check('Notify only broken builds')
   end
 
   step 'I should see service template settings saved' do
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index f0fbd8a826a839634226704c8c8802e967d8761c..63f0ec2b6e86dea2712621482591ecd03110e6f9 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -12,7 +12,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
   end
 
   step 'I should see "Shop" project CI status' do
-    expect(page).to have_link "Build status: skipped"
+    expect(page).to have_link "Build skipped"
   end
 
   step 'I should see last push widget' do
diff --git a/features/steps/explore/groups.rb b/features/steps/explore/groups.rb
index 87cd33c37eb2d187556e1d7222dcb803ba96cdcd..87f32e70d59257c2b9365c692451d12ad55848d1 100644
--- a/features/steps/explore/groups.rb
+++ b/features/steps/explore/groups.rb
@@ -75,18 +75,18 @@ class Spinach::Features::ExploreGroups < Spinach::FeatureSteps
       name: projectname,
       path: "#{groupname}-#{projectname}",
       visibility_level: visibility_level
-    )
+                    )
     create(:issue,
       title: "#{projectname} feature",
       project: project
-    )
+          )
     create(:merge_request,
       title: "#{projectname} feature implemented",
       source_project: project,
       target_project: project
-    )
+          )
     create(:closed_issue_event,
       project: project
-    )
+          )
   end
 end
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index 8b498e7b4a647d7bce3df82e7e22d6a92d4ebeb7..742ba5d71f66680adc64c3ea41da634543258509 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -2,6 +2,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
   include SharedAuthentication
   include SharedPaths
   include SharedProject
+  include SharedUser
 
   step 'I should see project "Empty Public Project"' do
     expect(page).to have_content "Empty Public Project"
@@ -60,11 +61,11 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
     create(:issue,
        title: "Bug",
        project: public_project
-      )
+          )
     create(:issue,
        title: "New feature",
        project: public_project
-      )
+          )
     visit namespace_project_issues_path(public_project.namespace, public_project)
   end
 
@@ -79,11 +80,11 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
     create(:issue,
        title: "Internal Bug",
        project: internal_project
-      )
+          )
     create(:issue,
        title: "New internal feature",
        project: internal_project
-      )
+          )
     visit namespace_project_issues_path(internal_project.namespace, internal_project)
   end
 
@@ -103,7 +104,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
       title: "Bug fix for public project",
       source_project: public_project,
       target_project: public_project,
-    )
+          )
   end
 
   step 'I should see list of merge requests for "Community" project' do
@@ -120,7 +121,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
       title: "Feature implemented",
       source_project: internal_project,
       target_project: internal_project
-    )
+          )
   end
 
   step 'I should see list of merge requests for "Internal" project' do
diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0706df3aec5d709e923f27d79dbd61839beb5095
--- /dev/null
+++ b/features/steps/group/members.rb
@@ -0,0 +1,147 @@
+class Spinach::Features::GroupMembers < Spinach::FeatureSteps
+  include SharedAuthentication
+  include SharedPaths
+  include SharedGroup
+  include SharedUser
+  include Select2Helper
+
+  step 'I select "Mike" as "Reporter"' do
+    user = User.find_by(name: "Mike")
+
+    page.within ".users-group-form" do
+      select2(user.id, from: "#user_ids", multiple: true)
+      select "Reporter", from: "access_level"
+    end
+
+    click_button "Add users to group"
+  end
+
+  step 'I select "Mike" as "Master"' do
+    user = User.find_by(name: "Mike")
+
+    page.within ".users-group-form" do
+      select2(user.id, from: "#user_ids", multiple: true)
+      select "Master", from: "access_level"
+    end
+
+    click_button "Add users to group"
+  end
+
+  step 'I should see "Mike" in team list as "Reporter"' do
+    page.within '.content-list' do
+      expect(page).to have_content('Mike')
+      expect(page).to have_content('Reporter')
+    end
+  end
+
+  step 'I should see "Mike" in team list as "Owner"' do
+    page.within '.content-list' do
+      expect(page).to have_content('Mike')
+      expect(page).to have_content('Owner')
+    end
+  end
+
+  step 'I select "sjobs@apple.com" as "Reporter"' do
+    page.within ".users-group-form" do
+      select2("sjobs@apple.com", from: "#user_ids", multiple: true)
+      select "Reporter", from: "access_level"
+    end
+
+    click_button "Add users to group"
+  end
+
+  step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do
+    page.within '.content-list' do
+      expect(page).to have_content('sjobs@apple.com')
+      expect(page).to have_content('invited')
+      expect(page).to have_content('Reporter')
+    end
+  end
+
+  step 'I select user "Mary Jane" from list with role "Reporter"' do
+    user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane")
+
+    page.within ".users-group-form" do
+      select2(user.id, from: "#user_ids", multiple: true)
+      select "Reporter", from: "access_level"
+    end
+
+    click_button "Add users to group"
+  end
+
+  step 'I should see user "John Doe" in team list' do
+    expect(group_members_list).to have_content("John Doe")
+  end
+
+  step 'I should not see user "John Doe" in team list' do
+    expect(group_members_list).not_to have_content("John Doe")
+  end
+
+  step 'I should see user "Mary Jane" in team list' do
+    expect(group_members_list).to have_content("Mary Jane")
+  end
+
+  step 'I should not see user "Mary Jane" in team list' do
+    expect(group_members_list).not_to have_content("Mary Jane")
+  end
+
+  step 'I click on the "Remove User From Group" button for "John Doe"' do
+    find(:css, 'li', text: "John Doe").find(:css, 'a.btn-remove').click
+    # poltergeist always confirms popups.
+  end
+
+  step 'I click on the "Remove User From Group" button for "Mary Jane"' do
+    find(:css, 'li', text: "Mary Jane").find(:css, 'a.btn-remove').click
+    # poltergeist always confirms popups.
+  end
+
+  step 'I should not see the "Remove User From Group" button for "John Doe"' do
+    expect(find(:css, 'li', text: "John Doe")).not_to have_selector(:css, 'a.btn-remove')
+    # poltergeist always confirms popups.
+  end
+
+  step 'I should not see the "Remove User From Group" button for "Mary Jane"' do
+    expect(find(:css, 'li', text: "Mary Jane")).not_to have_selector(:css, 'a.btn-remove')
+    # poltergeist always confirms popups.
+  end
+
+  step 'I search for \'Mary\' member' do
+    page.within '.member-search-form' do
+      fill_in 'search', with: 'Mary'
+      click_button 'Search'
+    end
+  end
+
+  step 'I change the "Mary Jane" role to "Developer"' do
+    member = mary_jane_member
+
+    page.within "#group_member_#{member.id}" do
+      find(".js-toggle-button").click
+      page.within "#edit_group_member_#{member.id}" do
+        select 'Developer', from: 'group_member_access_level'
+        click_on 'Save'
+      end
+    end
+  end
+
+  step 'I should see "Mary Jane" as "Developer"' do
+    member = mary_jane_member
+
+    page.within "#group_member_#{member.id}" do
+      page.within '.member-access-level' do
+        expect(page).to have_content "Developer"
+      end
+    end
+  end
+
+  private
+
+  def mary_jane_member
+    user = User.find_by(name: "Mary Jane")
+    owned_group.members.find_by(user_id: user.id)
+  end
+
+  def group_members_list
+    find(".panel .content-list")
+  end
+end
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6e57b16ccb69abde504d8b5a12b5811a34d2187a
--- /dev/null
+++ b/features/steps/group/milestones.rb
@@ -0,0 +1,90 @@
+class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
+  include SharedAuthentication
+  include SharedPaths
+  include SharedGroup
+  include SharedUser
+
+  step 'I click on group milestones' do
+    click_link 'Milestones'
+  end
+
+  step 'I should see group milestones index page has no milestones' do
+    expect(page).to have_content('No milestones to show')
+  end
+
+  step 'Group has projects with milestones' do
+    group_milestone
+  end
+
+  step 'I should see group milestones index page with milestones' do
+    expect(page).to have_content('Version 7.2')
+    expect(page).to have_content('GL-113')
+    expect(page).to have_link('3 Issues', href: issues_group_path("owned", milestone_title: "Version 7.2"))
+    expect(page).to have_link('0 Merge Requests', href: merge_requests_group_path("owned", milestone_title: "GL-113"))
+  end
+
+  step 'I click on one group milestone' do
+    click_link 'GL-113'
+  end
+
+  step 'I should see group milestone with descriptions and expiry date' do
+    expect(page).to have_content('expires at Aug 20, 2114')
+  end
+
+  step 'I should see group milestone with all issues and MRs assigned to that milestone' do
+    expect(page).to have_content('Milestone GL-113')
+    expect(page).to have_content('Progress: 0 closed – 3 open')
+    issue = Milestone.find_by(name: 'GL-113').issues.first
+    expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue))
+  end
+
+  step 'I fill milestone name' do
+    fill_in 'milestone_title', with: 'v2.9.0'
+  end
+
+  step 'I click new milestone button' do
+    click_link "New Milestone"
+  end
+
+  step 'I press create mileston button' do
+    click_button "Create Milestone"
+  end
+
+  step 'milestone in each project should be created' do
+    group = Group.find_by(name: 'Owned')
+    expect(page).to have_content "Milestone v2.9.0"
+    expect(group.projects).to be_present
+
+    group.projects.each do |project|
+      expect(page).to have_content project.name
+    end
+  end
+
+  private
+
+  def group_milestone
+    group = owned_group
+
+    %w(gitlabhq gitlab-ci cookbook-gitlab).each do |path|
+      project = create :project, path: path, group: group
+      milestone = create :milestone, title: "Version 7.2", project: project
+      create :issue,
+        project: project,
+        assignee: current_user,
+        author: current_user,
+        milestone: milestone
+
+      milestone = create :milestone,
+        title: "GL-113",
+        project: project,
+        due_date: '2114-08-20',
+        description: 'Lorem Ipsum is simply dummy text'
+
+      create :issue,
+        project: project,
+        assignee: current_user,
+        author: current_user,
+        milestone: milestone
+    end
+  end
+end
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 9c0313537b12db5573d86d1e956494dfc34e543c..4c5122d1b7d505b9d0afe2dae55972cf3e63a541 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -3,20 +3,11 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
   include SharedPaths
   include SharedGroup
   include SharedUser
-  include Select2Helper
 
   step 'I should see back to dashboard button' do
     expect(page).to have_content 'Go to dashboard'
   end
 
-  step 'gitlab user "Mike"' do
-    create(:user, name: "Mike")
-  end
-
-  step 'I click link "Add members"' do
-    find(:css, 'button.btn-new').click
-  end
-
   step 'I should see group "Owned"' do
     expect(page).to have_content '@owned'
   end
@@ -26,7 +17,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
   end
 
   step 'Group "Owned" has a public project "Public-project"' do
-    group = Group.find_by(name: "Owned")
+    group = owned_group
 
     @project = create :empty_project, :public,
                  group: group,
@@ -37,61 +28,8 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
     expect(page).to have_content 'Public-project'
   end
 
-  step 'I select "Mike" as "Reporter"' do
-    user = User.find_by(name: "Mike")
-
-    page.within ".users-group-form" do
-      select2(user.id, from: "#user_ids", multiple: true)
-      select "Reporter", from: "access_level"
-    end
-
-    click_button "Add users to group"
-  end
-
-  step 'I select "Mike" as "Master"' do
-    user = User.find_by(name: "Mike")
-
-    page.within ".users-group-form" do
-      select2(user.id, from: "#user_ids", multiple: true)
-      select "Master", from: "access_level"
-    end
-
-    click_button "Add users to group"
-  end
-
-  step 'I should see "Mike" in team list as "Reporter"' do
-    page.within '.well-list' do
-      expect(page).to have_content('Mike')
-      expect(page).to have_content('Reporter')
-    end
-  end
-
-  step 'I should see "Mike" in team list as "Owner"' do
-    page.within '.well-list' do
-      expect(page).to have_content('Mike')
-      expect(page).to have_content('Owner')
-    end
-  end
-
-  step 'I select "sjobs@apple.com" as "Reporter"' do
-    page.within ".users-group-form" do
-      select2("sjobs@apple.com", from: "#user_ids", multiple: true)
-      select "Reporter", from: "access_level"
-    end
-
-    click_button "Add users to group"
-  end
-
-  step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do
-    page.within '.well-list' do
-      expect(page).to have_content('sjobs@apple.com')
-      expect(page).to have_content('invited')
-      expect(page).to have_content('Reporter')
-    end
-  end
-
   step 'I should see group "Owned" projects list' do
-    Group.find_by(name: "Owned").projects.each do |project|
+    owned_group.projects.each do |project|
       expect(page).to have_link project.name
     end
   end
@@ -112,36 +50,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
     end
   end
 
-  step 'I select user "Mary Jane" from list with role "Reporter"' do
-    user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane")
-    click_button 'Add members'
-    page.within ".users-group-form" do
-      select2(user.id, from: "#user_ids", multiple: true)
-      select "Reporter", from: "access_level"
-    end
-    click_button "Add users to group"
-  end
-
-  step 'I should see user "John Doe" in team list' do
-    projects_with_access = find(".panel .well-list")
-    expect(projects_with_access).to have_content("John Doe")
-  end
-
-  step 'I should not see user "John Doe" in team list' do
-    projects_with_access = find(".panel .well-list")
-    expect(projects_with_access).not_to have_content("John Doe")
-  end
-
-  step 'I should see user "Mary Jane" in team list' do
-    projects_with_access = find(".panel .well-list")
-    expect(projects_with_access).to have_content("Mary Jane")
-  end
-
-  step 'I should not see user "Mary Jane" in team list' do
-    projects_with_access = find(".panel .well-list")
-    expect(projects_with_access).not_to have_content("Mary Jane")
-  end
-
   step 'project from group "Owned" has issues assigned to me' do
     create :issue,
       project: project,
@@ -172,12 +80,12 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
   step 'I change group "Owned" avatar' do
     attach_file(:group_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
     click_button "Save group"
-    Group.find_by(name: "Owned").reload
+    owned_group.reload
   end
 
   step 'I should see new group "Owned" avatar' do
-    expect(Group.find_by(name: "Owned").avatar).to be_instance_of AvatarUploader
-    expect(Group.find_by(name: "Owned").avatar.url).to eq "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/banana_sample.gif"
+    expect(owned_group.avatar).to be_instance_of AvatarUploader
+    expect(owned_group.avatar.url).to eq "/uploads/group/avatar/#{Group.find_by(name:"Owned").id}/banana_sample.gif"
   end
 
   step 'I should see the "Remove avatar" button' do
@@ -187,83 +95,22 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
   step 'I have group "Owned" avatar' do
     attach_file(:group_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
     click_button "Save group"
-    Group.find_by(name: "Owned").reload
+    owned_group.reload
   end
 
   step 'I remove group "Owned" avatar' do
     click_link "Remove avatar"
-    Group.find_by(name: "Owned").reload
+    owned_group.reload
   end
 
   step 'I should not see group "Owned" avatar' do
-    expect(Group.find_by(name: "Owned").avatar?).to eq false
+    expect(owned_group.avatar?).to eq false
   end
 
   step 'I should not see the "Remove avatar" button' do
     expect(page).not_to have_link("Remove avatar")
   end
 
-  step 'I click on the "Remove User From Group" button for "John Doe"' do
-    find(:css, 'li', text: "John Doe").find(:css, 'a.btn-remove').click
-    # poltergeist always confirms popups.
-  end
-
-  step 'I click on the "Remove User From Group" button for "Mary Jane"' do
-    find(:css, 'li', text: "Mary Jane").find(:css, 'a.btn-remove').click
-    # poltergeist always confirms popups.
-  end
-
-  step 'I should not see the "Remove User From Group" button for "John Doe"' do
-    expect(find(:css, 'li', text: "John Doe")).not_to have_selector(:css, 'a.btn-remove')
-    # poltergeist always confirms popups.
-  end
-
-  step 'I should not see the "Remove User From Group" button for "Mary Jane"' do
-    expect(find(:css, 'li', text: "Mary Jane")).not_to have_selector(:css, 'a.btn-remove')
-    # poltergeist always confirms popups.
-  end
-
-  step 'I search for \'Mary\' member' do
-    page.within '.member-search-form' do
-      fill_in 'search', with: 'Mary'
-      click_button 'Search'
-    end
-  end
-
-  step 'I click on group milestones' do
-    click_link 'Milestones'
-  end
-
-  step 'I should see group milestones index page has no milestones' do
-    expect(page).to have_content('No milestones to show')
-  end
-
-  step 'Group has projects with milestones' do
-    group_milestone
-  end
-
-  step 'I should see group milestones index page with milestones' do
-    expect(page).to have_content('Version 7.2')
-    expect(page).to have_content('GL-113')
-    expect(page).to have_link('2 Issues', href: issues_group_path("owned", milestone_title: "Version 7.2"))
-    expect(page).to have_link('3 Merge Requests', href: merge_requests_group_path("owned", milestone_title: "GL-113"))
-  end
-
-  step 'I click on one group milestone' do
-    click_link 'GL-113'
-  end
-
-  step 'I should see group milestone with descriptions and expiry date' do
-    expect(page).to have_content('expires at Aug 20, 2114')
-  end
-
-  step 'I should see group milestone with all issues and MRs assigned to that milestone' do
-    expect(page).to have_content('Milestone GL-113')
-    expect(page).to have_content('Progress: 0 closed – 4 open')
-    expect(page).to have_link(@issue1.title, href: namespace_project_issue_path(@project1.namespace, @project1, @issue1))
-    expect(page).to have_link(@mr3.title, href: namespace_project_merge_request_path(@project3.namespace, @project3, @mr3))
-  end
-
   step 'Group "Owned" has archived project' do
     group = Group.find_by(name: 'Owned')
     create(:project, namespace: group, archived: true, path: "archived-project")
@@ -273,101 +120,13 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
     expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
   end
 
-  step 'I fill milestone name' do
-    fill_in 'milestone_title', with: 'v2.9.0'
-  end
-
-  step 'I click new milestone button' do
-    click_link "New Milestone"
-  end
-
-  step 'I press create mileston button' do
-    click_button "Create Milestone"
-  end
-
-  step 'milestone in each project should be created' do
-    group = Group.find_by(name: 'Owned')
-    expect(page).to have_content "Milestone v2.9.0"
-    expect(group.projects).to be_present
-
-    group.projects.each do |project|
-      expect(page).to have_content project.name
-    end
-  end
-
-  protected
+  private
 
   def assigned_to_me(key)
     project.send(key).where(assignee_id: current_user.id)
   end
 
   def project
-    Group.find_by(name: "Owned").projects.first
-  end
-
-  def group_milestone
-    group = Group.find_by(name: "Owned")
-
-    @project1 = create :project,
-                 group: group
-    project2 = create :project,
-                 path: 'gitlab-ci',
-                 group: group
-    @project3 = create :project,
-                 path: 'cookbook-gitlab',
-                 group: group
-    milestone1_project1 = create :milestone,
-                            title: "Version 7.2",
-                            project: @project1
-    milestone1_project2 = create :milestone,
-                            title: "Version 7.2",
-                            project: project2
-    create :milestone,
-      title: "Version 7.2",
-      project: @project3
-    milestone2_project1 = create :milestone,
-                            title: "GL-113",
-                            project: @project1
-    milestone2_project2 = create :milestone,
-                            title: "GL-113",
-                            project: project2
-    milestone2_project3 = create :milestone,
-                            title: "GL-113",
-                            project: @project3,
-                            due_date: '2114-08-20',
-                            description: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry'
-    @issue1 = create :issue,
-               project: @project1,
-               assignee: current_user,
-               author: current_user,
-               milestone: milestone2_project1
-    create :issue,
-      project: project2,
-      assignee: current_user,
-      author: current_user,
-      milestone: milestone1_project2
-    create :issue,
-      project: @project3,
-      assignee: current_user,
-      author: current_user,
-      milestone: milestone1_project1
-    create :merge_request,
-      source_project: @project1,
-      target_project: @project1,
-      assignee: current_user,
-      author: current_user,
-      milestone: milestone2_project1
-    create :merge_request,
-      source_project: project2,
-      target_project: project2,
-      assignee: current_user,
-      author: current_user,
-      milestone: milestone2_project2
-    @mr3 = create :merge_request,
-            source_project: @project3,
-            target_project: @project3,
-            assignee: current_user,
-            author: current_user,
-            milestone: milestone2_project3
+    owned_group.projects.first
   end
 end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 40b2aa7c357761bde578679b678d1bf39a1cb6ae..0305f7e6da0bc76e7ae831e86238505a7a3115c8 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -34,7 +34,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
 
   step 'I should see new avatar' do
     expect(@user.avatar).to be_instance_of AvatarUploader
-    expect(@user.avatar.url).to eq "/uploads/user/avatar/#{ @user.id }/banana_sample.gif"
+    expect(@user.avatar.url).to eq "/uploads/user/avatar/#{@user.id}/banana_sample.gif"
   end
 
   step 'I should see the "Remove avatar" button' do
diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb
index 338f5e8d3ee66ce7c2ed50bf4f3695b2e7a6e614..0a42931147dc1ee7f78562f97c6fe1172ec1fec1 100644
--- a/features/steps/project/commits/branches.rb
+++ b/features/steps/project/commits/branches.rb
@@ -61,7 +61,8 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
   end
 
   step 'I should see new an error that branch is invalid' do
-    expect(page).to have_content 'Branch name invalid'
+    expect(page).to have_content 'Branch name is invalid'
+    expect(page).to have_content "can't contain spaces"
   end
 
   step 'I should see new an error that ref is invalid' do
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index e5b3f27135da84d9ec21e8b0dc0cf23a7d032904..a3141fe3be12cff20840f34494236090adb31857 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -104,10 +104,14 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
 
   step 'commit has ci status' do
     @project.enable_ci
-    ci_commit = create :ci_commit, gl_project: @project, sha: sample_commit.id
+    ci_commit = create :ci_commit, project: @project, sha: sample_commit.id
     create :ci_build, commit: ci_commit
   end
 
+  step 'repository contains ".gitlab-ci.yml" file' do
+    allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file).and_return(String.new)
+  end
+
   step 'I see commit ci info' do
     expect(page).to have_content "build: pending"
   end
@@ -118,6 +122,6 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
 
   step 'I see builds list' do
     expect(page).to have_content "build: pending"
-    expect(page).to have_content "Latest builds"
+    expect(page).to have_content "1 build"
   end
 end
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index 0d39e1997b57552b50b2b564b96d5bc6e918d925..8a0e8fc2b6c8998937c4140558cc882e877d5a20 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -1,6 +1,7 @@
 class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
   include SharedAuthentication
   include SharedPaths
+  include SharedUser
 
   step 'fill project form with valid data' do
     fill_in 'project_path', with: 'Empty'
@@ -25,7 +26,8 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
   end
 
   step 'I click on HTTP' do
-    click_button 'HTTP'
+    find('#clone-dropdown').click
+    find('#http-selector').click
   end
 
   step 'Remote url should update to http link' do
@@ -33,7 +35,8 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
   end
 
   step 'If I click on SSH' do
-    click_button 'SSH'
+    find('#clone-dropdown').click
+    find('#ssh-selector').click
   end
 
   step 'Remote url should update to ssh link' do
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 2a333222fb29b024a6e0e7b3ad164d8a04f5ca1a..cbdce78dc0ca300923c36096b632463d35821e09 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -39,14 +39,15 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
     select "fix", from: "merge_request_source_branch"
     select "master", from: "merge_request_target_branch"
 
-    click_button "Compare branches"
+    click_button "Compare branches and continue"
+
+    expect(page).to have_css("h3.page-title", text: "New Merge Request")
 
-    expect(page).to have_content "New merge request"
     fill_in "merge_request_title", with: "Merge Request On Forked Project"
   end
 
   step 'I submit the merge request' do
-    click_button "Submit new merge request"
+    click_button "Submit merge request"
   end
 
   step 'I follow the target commit link' do
@@ -112,11 +113,10 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
-    select "Select branch", from: "merge_request_target_branch"
     expect(find(:select, "merge_request_source_project_id", {}).value).to eq @forked_project.id.to_s
     expect(find(:select, "merge_request_target_project_id", {}).value).to eq @project.id.to_s
     expect(find(:select, "merge_request_source_branch", {}).value).to eq ""
-    expect(find(:select, "merge_request_target_branch", {}).value).to eq ""
+    expect(find(:select, "merge_request_target_branch", {}).value).to eq "master"
     click_button "Compare branches"
   end
 
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
index 98f31f3b76a194d854087e70264c45910dc5f24f..b09ec86e5dfa7af77c1334f9b12777f92b0086ac 100644
--- a/features/steps/project/graph.rb
+++ b/features/steps/project/graph.rb
@@ -14,6 +14,15 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
     visit commits_namespace_project_graph_path(project.namespace, project, "master")
   end
 
+  step 'I visit project "Shop" languages graph page' do
+    visit languages_namespace_project_graph_path(project.namespace, project, "master")
+  end
+
+  step 'page should have languages graphs' do
+    expect(page).to have_content "Ruby 66.63 %"
+    expect(page).to have_content "JavaScript 22.96 %"
+  end
+
   step 'page should have commits graphs' do
     expect(page).to have_content "Commit statistics for master"
     expect(page).to have_content "Commits per day of month"
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index df4a23a37169a5759a18e842980100b214518110..be4db770948cc3391592c4a79251930be02f943a 100644
--- a/features/steps/project/hooks.rb
+++ b/features/steps/project/hooks.rb
@@ -70,8 +70,6 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
 
   step 'I should see hook service down error message' do
     expect(page).to have_selector '.flash-alert',
-                              text: 'Hook execution failed. '\
-                                    'Ensure hook URL is correct and '\
-                                    'service is up.'
+                              text: 'Hook execution failed: Exception from'
   end
 end
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 8f7a45dec0eb0a77137e36264569a84c6fd11128..1404f34cfe0eebad8f44f7224cee0ffdf452fabb 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -9,33 +9,61 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
   end
 
   step 'I click to emoji-picker' do
-    page.within ".awards-controls" do
-      page.find(".add-award").click
+    page.within '.awards-controls' do
+      page.find('.add-award').click
     end
   end
 
   step 'I click to emoji in the picker' do
-    page.within ".awards-menu" do
-      page.first("img").click
+    page.within '.emoji-menu-content' do
+      page.first('.emoji-icon').click
     end
   end
 
   step 'I can remove it by clicking to icon' do
-    page.within ".awards" do
-      page.first(".award").click
-      expect(page).to_not have_selector ".award"
+    page.within '.awards' do
+      expect do
+        page.find('.award.active').click
+        sleep 0.1
+      end.to change{ page.all(".award").size }.from(3).to(2)
+    end
+  end
+
+  step 'I can see the activity and food categories' do
+    page.within '.emoji-menu' do
+      expect(page).to_not have_selector 'Activity'
+      expect(page).to_not have_selector 'Food'
     end
   end
 
   step 'I have award added' do
-    page.within ".awards" do
-      expect(page).to have_selector ".award"
-      expect(page.find(".award .counter")).to have_content "1"
+    page.within '.awards' do
+      expect(page).to have_selector '.award'
+      expect(page.find('.award.active .counter')).to have_content '1'
     end
   end
 
   step 'project "Shop" has issue "Bugfix"' do
-    @project = Project.find_by(name: "Shop")
-    @issue = create(:issue, title: "Bugfix", project: project)
+    @project = Project.find_by(name: 'Shop')
+    @issue = create(:issue, title: 'Bugfix', project: project)
+  end
+
+  step 'I leave comment with a single emoji' do
+    page.within('.js-main-target-form') do
+      fill_in 'note[note]', with: ':smile:'
+      click_button 'Add Comment'
+    end
+  end
+
+  step 'I search "hand"' do
+    page.within('.emoji-menu-content') do
+      fill_in 'emoji_search', with: 'hand'
+    end
+  end
+
+  step 'I see search result for "hand"' do
+    page.within '.emoji-menu-content' do
+      expect(page).to have_selector '[data-emoji="raised_hand"]'
+    end
   end
 end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index af2da41badb220f0fa07e965fc9d079242a43d96..4a7ff21d385334f159f83034dea163727efa9ee8 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -65,20 +65,20 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
   step 'I see current user as the first user' do
     expect(page).to have_selector('.user-result', visible: true, count: 4)
     users = page.all('.user-name')
-    expect(users[0].text).to eq 'Any'
+    expect(users[0].text).to eq 'Any Assignee'
     expect(users[1].text).to eq 'Unassigned'
     expect(users[2].text).to eq current_user.name
   end
 
   step 'I submit new issue "500 error on profile"' do
     fill_in "issue_title", with: "500 error on profile"
-    click_button "Submit new issue"
+    click_button "Submit issue"
   end
 
   step 'I submit new issue "500 error on profile" with label \'bug\'' do
     fill_in "issue_title", with: "500 error on profile"
     select 'bug', from: "Labels"
-    click_button "Submit new issue"
+    click_button "Submit issue"
   end
 
   step 'I click link "500 error on profile"' do
@@ -86,7 +86,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
   end
 
   step 'I should see label \'bug\' with issue' do
-    page.within '.issue-show-labels' do
+    page.within '.issuable-show-labels' do
       expect(page).to have_content 'bug'
     end
   end
@@ -284,6 +284,16 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
     end
   end
 
+  step 'another user adds a comment with text "Yay!" to issue "Release 0.4"' do
+    issue = Issue.find_by!(title: 'Release 0.4')
+    create(:note_on_issue, noteable: issue,  note: 'Yay!')
+  end
+
+  step 'I should see a new comment with text "Yay!"' do
+    page.within '#notes' do
+      expect(page).to have_content('Yay!')
+    end
+  end
   def filter_issue(text)
     fill_in 'issue_search', with: text
   end
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index d656acf4220c1f8d20b0bde8c90f2d28e37f0349..2ab8956867bd780ba1e83b323bc36baa3aa57bb8 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -9,7 +9,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
 
   step 'I remove label \'bug\'' do
     page.within "#label_#{bug_label.id}" do
-      click_link 'Remove'
+      click_link 'Delete'
     end
   end
 
@@ -31,20 +31,20 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
 
   step 'I submit new label \'support\'' do
     fill_in 'Title', with: 'support'
-    fill_in 'Background Color', with: '#F95610'
-    click_button 'Save'
+    fill_in 'Background color', with: '#F95610'
+    click_button 'Create Label'
   end
 
   step 'I submit new label \'bug\'' do
     fill_in 'Title', with: 'bug'
-    fill_in 'Background Color', with: '#F95610'
-    click_button 'Save'
+    fill_in 'Background color', with: '#F95610'
+    click_button 'Create Label'
   end
 
   step 'I submit new label with invalid color' do
     fill_in 'Title', with: 'support'
-    fill_in 'Background Color', with: '#12'
-    click_button 'Save'
+    fill_in 'Background color', with: '#12'
+    click_button 'Create Label'
   end
 
   step 'I should see label label exist error message' do
@@ -55,7 +55,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
 
   step 'I should see label color error message' do
     page.within '.label-form' do
-      expect(page).to have_content 'Color is invalid'
+      expect(page).to have_content 'Color must be a valid color code'
     end
   end
 
@@ -85,8 +85,8 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
 
   step 'I change label \'bug\' to \'fix\'' do
     fill_in 'Title', with: 'fix'
-    fill_in 'Background Color', with: '#F15610'
-    click_button 'Save'
+    fill_in 'Background color', with: '#F15610'
+    click_button 'Save changes'
   end
 
   step 'I should see label \'fix\'' do
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index c8708572ec69af4be20a4e52f203ac02809496d9..e2eda511497f4d6e4bb7974e41f94b8046d04768 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -63,7 +63,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
   end
 
   step 'I click link to remove milestone' do
-    click_link 'Remove'
+    click_link 'Delete'
   end
 
   step 'I should see no milestones' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index d5f2c4209a12376b4727287621f3ee35758d36ff..be993d11093edf42d08122f946436980361575b4 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -86,7 +86,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
     select "feature", from: "merge_request_target_branch"
     click_button "Compare branches"
     fill_in "merge_request_title", with: "Wiki Feature"
-    click_button "Submit new merge request"
+    click_button "Submit merge request"
   end
 
   step 'project "Shop" have "Bug NS-04" open merge request' do
@@ -186,6 +186,50 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
     leave_comment "Line is wrong"
   end
 
+  step 'I change the comment "Line is wrong" to "Typo, please fix" on diff' do
+    page.within('.diff-file:nth-of-type(5) .note') do
+      find('.js-note-edit').click
+
+      page.within('.current-note-edit-form', visible: true) do
+        fill_in 'note_note', with: 'Typo, please fix'
+        click_button 'Save Comment'
+      end
+
+      expect(page).not_to have_button 'Save Comment', disabled: true, visible: true
+    end
+  end
+
+  step 'I should not see a diff comment saying "Line is wrong"' do
+    page.within('.diff-file:nth-of-type(5) .note') do
+      expect(page).not_to have_visible_content 'Line is wrong'
+    end
+  end
+
+  step 'I should see a diff comment saying "Typo, please fix"' do
+    page.within('.diff-file:nth-of-type(5) .note') do
+      expect(page).to have_visible_content 'Typo, please fix'
+    end
+  end
+
+  step 'I delete the comment "Line is wrong" on diff' do
+    page.within('.diff-file:nth-of-type(5) .note') do
+      find('.js-note-delete').click
+    end
+  end
+
+  step 'I click on the Discussion tab' do
+    page.within '.merge-request-tabs' do
+      click_link 'Discussion'
+    end
+
+    # Waits for load
+    expect(page).to have_css('.tab-content #notes.active')
+  end
+
+  step 'I should not see any discussion' do
+    expect(page).not_to have_css('.notes .discussion')
+  end
+
   step 'I should see a discussion has started on diff' do
     page.within(".notes .discussion") do
       page.should have_content "#{current_user.name} started a discussion"
@@ -229,7 +273,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I should see merged request' do
-    page.within '.issue-box' do
+    page.within '.status-box' do
       expect(page).to have_content "Merged"
     end
   end
@@ -239,7 +283,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I should see reopened merge request "Bug NS-04"' do
-    page.within '.issue-box' do
+    page.within '.status-box' do
       expect(page).to have_content "Open"
     end
   end
@@ -361,13 +405,13 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
   step '"Bug NS-05" has CI status' do
     project = merge_request.source_project
     project.enable_ci
-    ci_commit = create :ci_commit, gl_project: project, sha: merge_request.last_commit.id
+    ci_commit = create :ci_commit, project: project, sha: merge_request.last_commit.id
     create :ci_build, commit: ci_commit
   end
 
   step 'I should see merge request "Bug NS-05" with CI status' do
     page.within ".mr-list" do
-      expect(page).to have_link "Build status: pending"
+      expect(page).to have_link "Build pending"
     end
   end
 
diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2685f5fd6b4b5f891fe4fb75afb7128d55fbd0f2
--- /dev/null
+++ b/features/steps/project/merge_requests/acceptance.rb
@@ -0,0 +1,43 @@
+class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
+  include LoginHelpers
+  include GitlabRoutingHelper
+
+  step 'I am on the Merge Request detail page' do
+    visit merge_request_path(@merge_request)
+  end
+
+  step 'I am on the Merge Request detail with note anchor page' do
+    visit merge_request_path(@merge_request, anchor: 'note_123')
+  end
+
+  step 'I click on "Remove source branch" option' do
+    check('Remove source branch')
+  end
+
+  step 'I click on Accept Merge Request' do
+    click_button('Accept Merge Request')
+  end
+
+  step 'I should see the Remove Source Branch button' do
+    expect(page).to have_link('Remove Source Branch')
+  end
+
+  step 'I should not see the Remove Source Branch button' do
+    expect(page).not_to have_link('Remove Source Branch')
+  end
+
+  step 'There is an open Merge Request' do
+    @user = create(:user)
+    @project = create(:project, :public)
+    @project_member = create(:project_member, user: @user, project: @project, access_level: ProjectMember::DEVELOPER)
+    @merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project)
+  end
+
+  step 'I am signed in as a developer of the project' do
+    login_as(@user)
+  end
+
+  step 'I should see merge request merged' do
+    expect(page).to have_content('The changes were merged into')
+  end
+end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 9ca7c8ebbc7ef8ed8efcbeb3f7f137d14e16c5eb..37bf52b4a95ac1eda2d568a60fd24015c33d6f86 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -37,7 +37,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
   step 'I should see new project avatar' do
     expect(@project.avatar).to be_instance_of AvatarUploader
     url = @project.avatar.url
-    expect(url).to eq "/uploads/project/avatar/#{ @project.id }/banana_sample.gif"
+    expect(url).to eq "/uploads/project/avatar/#{@project.id}/banana_sample.gif"
   end
 
   step 'I should see the "Remove avatar" button' do
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 1c700df0c631cc64bdbc7fc99764558f57d438cf..536199ddb4fd7c25f766b7a93b5a74dd992490e0 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -11,7 +11,6 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
     expect(page).to have_content 'Project services'
     expect(page).to have_content 'Campfire'
     expect(page).to have_content 'HipChat'
-    expect(page).to have_content 'GitLab CI'
     expect(page).to have_content 'Assembla'
     expect(page).to have_content 'Pushover'
     expect(page).to have_content 'Atlassian Bamboo'
@@ -20,15 +19,6 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
     expect(page).to have_content 'Irker (IRC gateway)'
   end
 
-  step 'I click gitlab-ci service link' do
-    click_link 'GitLab CI'
-  end
-
-  step 'I fill gitlab-ci settings' do
-    check 'Active'
-    click_button 'Save'
-  end
-
   step 'I should see service settings saved' do
     expect(find_field('Active').value).to eq '1'
   end
@@ -183,6 +173,24 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
     expect(find_field('Sound').find('option[selected]').value).to eq 'bike'
   end
 
+  step 'I click jira service link' do
+    click_link 'JIRA'
+  end
+
+  step 'I fill jira settings' do
+    fill_in 'Project url', with: 'http://jira.example'
+    fill_in 'Username', with: 'gitlab'
+    fill_in 'Password', with: 'gitlab'
+    fill_in 'Api url', with: 'http://jira.example/rest/api/2'
+    click_button 'Save'
+  end
+
+  step 'I should see jira service settings saved' do
+    expect(find_field('Project url').value).to eq 'http://jira.example'
+    expect(find_field('Username').value).to eq 'gitlab'
+    expect(find_field('Api url').value).to eq 'http://jira.example/rest/api/2'
+  end
+
   step 'I click Atlassian Bamboo CI service link' do
     click_link 'Atlassian Bamboo CI'
   end
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index a3aef9bf8c30c417093bb70d34489fd6b05a93fd..504654f90ddd12a28980281778bcae256015bd23 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -42,7 +42,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
   end
 
   step 'I click link "Edit"' do
-    page.within ".page-title" do
+    page.within ".detail-page-header" do
       click_link "Edit"
     end
   end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index f40e0f0d5282c3f7bf13d9f34e9d4b50c2eec03b..d08935aa1010a9f10be706ba03f8e87edba1d53d 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -5,6 +5,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   include SharedPaths
   include RepoHelpers
 
+  step "I don't have write access" do
+    @project = create(:project, name: "Other Project", path: "other-project")
+    @project.team << [@user, :reporter]
+    visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+  end
+
   step 'I should see files from repository' do
     expect(page).to have_content "VERSION"
     expect(page).to have_content ".gitignore"
@@ -37,6 +43,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
     expect(page).to have_content new_gitignore_content
   end
 
+  step 'I should see its content with new lines preserved at end of file' do
+    expect(evaluate_script('blob.editor.getValue()')).to eq "Sample\n\n\n"
+  end
+
   step 'I click link "Raw"' do
     click_link 'Raw'
   end
@@ -53,10 +63,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
     expect(page).not_to have_link 'edit'
   end
 
-  step 'The edit button is disabled' do
-    expect(page).to have_css '.disabled', text: 'Edit'
-  end
-
   step 'I can edit code' do
     set_new_content
     expect(evaluate_script('blob.editor.getValue()')).to eq new_gitignore_content
@@ -66,12 +72,16 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
     set_new_content
   end
 
+  step 'I edit code with new lines at end of file' do
+    execute_script('blob.editor.setValue("Sample\n\n\n")')
+  end
+
   step 'I fill the new file name' do
     fill_in :file_name, with: new_file_name
   end
 
   step 'I fill the new branch name' do
-    fill_in :new_branch, with: 'new_branch_name', visible: true
+    fill_in :target_branch, with: 'new_branch_name', visible: true
   end
 
   step 'I fill the new file name with an illegal name' do
@@ -83,11 +93,11 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step 'I fill the commit message' do
-    fill_in :commit_message, with: 'Not yet a commit message.', visible: true
+    fill_in :commit_message, with: 'New commit message', visible: true
   end
 
   step 'I click link "Diff"' do
-    click_link 'Preview changes'
+    click_link 'Preview Changes'
   end
 
   step 'I click on "Commit Changes"' do
@@ -99,7 +109,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step 'I click on "Delete"' do
-    click_button 'Delete'
+    click_on 'Delete'
   end
 
   step 'I click on "Delete file"' do
@@ -107,7 +117,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step 'I click on "Replace"' do
-    click_button  "Replace"
+    click_on  "Replace"
   end
 
   step 'I click on "Replace file"' do
@@ -120,7 +130,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
 
   step 'I click on "New file" link in repo' do
     find('.add-to-tree').click
-    click_link 'Create file'
+    click_link 'New file'
   end
 
   step 'I click on "Upload file" link in repo' do
@@ -142,7 +152,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step 'I can see new file page' do
-    expect(page).to have_content "Create New File"
+    expect(page).to have_content "New File"
     expect(page).to have_content "Commit message"
   end
 
@@ -151,7 +161,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step 'I can see the new commit message' do
-    expect(page).to have_content "New upload commit message"
+    expect(page).to have_content "New commit message"
   end
 
   step 'I upload a new text file' do
@@ -160,7 +170,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
 
   step 'I fill the upload file commit message' do
     page.within('#modal-upload-blob') do
-      fill_in :commit_message, with: 'New upload commit message'
+      fill_in :commit_message, with: 'New commit message'
     end
   end
 
@@ -192,7 +202,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step 'I see Browse dir link' do
-    expect(page).to have_link 'Browse Dir »'
+    expect(page).to have_link 'Browse Directory »'
     expect(page).not_to have_link 'Browse Code »'
   end
 
@@ -204,13 +214,13 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
 
   step 'I see Browse file link' do
     expect(page).to have_link 'Browse File »'
-    expect(page).not_to have_link 'Browse Code »'
+    expect(page).not_to have_link 'Browse Files »'
   end
 
   step 'I see Browse code link' do
-    expect(page).to have_link 'Browse Code »'
+    expect(page).to have_link 'Browse Files »'
     expect(page).not_to have_link 'Browse File »'
-    expect(page).not_to have_link 'Browse Dir »'
+    expect(page).not_to have_link 'Browse Directory »'
   end
 
   step 'I click on Permalink' do
@@ -234,22 +244,27 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
   end
 
   step 'I am redirected to the new file' do
-    expect(current_path).to eq(namespace_project_blob_path(
-      @project.namespace, @project, 'master/' + new_file_name))
+    expect(current_path).to eq(
+      namespace_project_blob_path(@project.namespace, @project, 'master/' + new_file_name))
   end
 
   step 'I am redirected to the new file with directory' do
-    expect(current_path).to eq(namespace_project_blob_path(
-      @project.namespace, @project, 'master/' + new_file_name_with_directory))
+    expect(current_path).to eq(
+      namespace_project_blob_path(@project.namespace, @project, 'master/' + new_file_name_with_directory))
   end
 
   step 'I am redirected to the new merge request page' do
     expect(current_path).to eq(new_namespace_project_merge_request_path(@project.namespace, @project))
   end
 
+  step "I am redirected to the fork's new merge request page" do
+    fork = @user.fork_of(@project)
+    expect(current_path).to eq(new_namespace_project_merge_request_path(fork.namespace, fork))
+  end
+
   step 'I am redirected to the root directory' do
-    expect(current_path).to eq(namespace_project_tree_path(
-      @project.namespace, @project, 'master/'))
+    expect(current_path).to eq(
+      namespace_project_tree_path(@project.namespace, @project, 'master'))
   end
 
   step "I don't see the permalink link" do
@@ -305,6 +320,37 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
     expect(page).not_to have_content('Loading commit data...')
   end
 
+  step 'I click on "files/lfs/lfs_object.iso" file in repo' do
+    visit namespace_project_tree_path(@project.namespace, @project, "lfs")
+    click_link 'files'
+    click_link "lfs"
+    click_link "lfs_object.iso"
+  end
+
+  step 'I should see download link and object size' do
+    expect(page).to have_content 'Download (1.5 MB)'
+  end
+
+  step 'I should not see lfs pointer details' do
+    expect(page).not_to have_content 'version https://git-lfs.github.com/spec/v1'
+    expect(page).not_to have_content 'oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897'
+    expect(page).not_to have_content 'size 1575078'
+  end
+
+  step 'I should see buttons for allowed commands' do
+    expect(page).to have_content 'Raw'
+    expect(page).to have_content 'History'
+    expect(page).to have_content 'Permalink'
+    expect(page).not_to have_content 'Edit'
+    expect(page).not_to have_content 'Blame'
+    expect(page).to have_content 'Delete'
+    expect(page).to have_content 'Replace'
+  end
+
+  step 'I should see a notice about a new fork having been created' do
+    expect(page).to have_content "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request."
+  end
+
   private
 
   def set_new_content
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index c78e86fa1a731904d81f70512773c7f50cf8e4a5..3a4f7a6e01c4ebd24866afa7486a129550ebe717 100644
--- a/features/steps/project/source/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -238,7 +238,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
 
   step 'I see new wiki page named test' do
     expect(current_path).to eq  namespace_project_wiki_path(@project.namespace, @project, "test")
-    expect(page).to have_content "Editing"
+    expect(page).to have_content "Edit Page test"
   end
 
   When 'I go back to wiki page home' do
@@ -252,7 +252,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
 
   step 'I see Gitlab API document' do
     expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api")
-    expect(page).to have_content "Editing"
+    expect(page).to have_content "Edit Page api"
   end
 
   step 'I click on Rake tasks link' do
@@ -261,7 +261,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
 
   step 'I see Rake tasks directory' do
     expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks")
-    expect(page).to have_content "Editing"
+    expect(page).to have_content "Edit Page raketasks"
   end
 
   step 'I go directory which contains README file' do
diff --git a/features/steps/project/star.rb b/features/steps/project/star.rb
index bd2e0619cddd8be94d6717fec3d268a41313cc14..9f7c748a3b78ff3bae147189db11af8c430908e2 100644
--- a/features/steps/project/star.rb
+++ b/features/steps/project/star.rb
@@ -32,6 +32,6 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps
   protected
 
   def has_n_stars(n)
-    expect(page).to have_css(".star-btn .count", text: n, visible: true)
+    expect(page).to have_css(".star-count", text: n, visible: true)
   end
 end
diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb
index 97d63016458b41b0317108d13bb5423a29017311..caad52def794186ea37cabb59e7e4499dbc86d46 100644
--- a/features/steps/project/team_management.rb
+++ b/features/steps/project/team_management.rb
@@ -15,10 +15,6 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
     expect(page).to have_content(user.username)
   end
 
-  step 'I click link "Add members"' do
-    find(:css, 'button.btn-new').click
-  end
-
   step 'I select "Mike" as "Reporter"' do
     user = User.find_by(name: "Mike")
 
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 02207dbffa6dcfbd493bc1e16b68bab989dbcc9c..91d227fadbf6b440851e7a0f767bd091881c9b02 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -5,7 +5,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   include SharedPaths
 
   step 'I click on the Cancel button' do
-    page.within(:css, ".form-actions") do
+    page.within(:css, ".wiki-form .form-actions") do
       click_on "Cancel"
     end
   end
@@ -24,7 +24,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
     expect(page).to have_content "link test"
 
     click_link "link test"
-    expect(page).to have_content "Editing"
+    expect(page).to have_content "Edit Page"
   end
 
   step 'I have an existing Wiki page' do
@@ -68,7 +68,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   end
 
   step 'I click on the "Delete this page" button' do
-    click_on "Delete this page"
+    click_on "Delete"
   end
 
   step 'The page should be deleted' do
@@ -120,13 +120,13 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   step 'I should see the new wiki page form' do
     expect(current_path).to match('wikis/image.jpg')
     expect(page).to have_content('New Wiki Page')
-    expect(page).to have_content('Editing - image.jpg')
+    expect(page).to have_content('Edit Page image.jpg')
   end
 
   step 'I create a New page with paths' do
     click_on 'New Page'
     fill_in 'Page slug', with: 'one/two/three'
-    click_on 'Build'
+    click_on 'Create Page'
     fill_in "wiki_content", with: 'wiki content'
     click_on "Create page"
     expect(current_path).to include 'one/two/three'
@@ -135,7 +135,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   step 'I create a New page with an invalid name' do
     click_on 'New Page'
     fill_in 'Page slug', with: 'invalid name'
-    click_on 'Build'
+    click_on 'Create Page'
   end
 
   step 'I should see an error message' do
@@ -156,7 +156,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   end
 
   step 'I should see the Editing page' do
-    expect(page).to have_content('Editing')
+    expect(page).to have_content('Edit Page')
   end
 
   step 'I view the page history of a Wiki page that has a path' do
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 72621911a37151cd82b412d46d78465b3abb7380..c6a0ae2ba38e06110bce21311a248aaccd74d27c 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -87,6 +87,17 @@ module SharedDiffNote
     end
   end
 
+  step 'I write a diff comment like ":smile:"' do
+    page.within(diff_file_selector) do
+      click_diff_line(sample_commit.line_code)
+
+      page.within("form[rel$='#{sample_commit.line_code}']") do
+        fill_in 'note[note]', with: ':smile:'
+        click_button('Add Comment')
+      end
+    end
+  end
+
   step 'I submit the diff comment' do
     page.within(diff_file_selector) do
       click_button("Add Comment")
@@ -155,7 +166,7 @@ module SharedDiffNote
   end
 
   step 'I should see add a diff comment button' do
-    expect(page).to have_css('.js-add-diff-note-button', visible: true)
+    expect(page).to have_css('.js-add-diff-note-button')
   end
 
   step 'I should see an empty diff comment form' do
@@ -197,6 +208,12 @@ module SharedDiffNote
     end
   end
 
+  step 'I should see a diff comment with an emoji image' do
+    page.within("#{diff_file_selector} .note") do
+      expect(page).to have_xpath("//img[@alt=':smile:']")
+    end
+  end
+
   step 'I click side-by-side diff button' do
     find('#parallel-diff-btn').trigger('click')
   end
diff --git a/features/steps/shared/group.rb b/features/steps/shared/group.rb
index 83a04576973d9e2e7f7602e26bb2b0f45c364d03..fe6736dacd42989ff9b1919244e2939e5fa7d114 100644
--- a/features/steps/shared/group.rb
+++ b/features/steps/shared/group.rb
@@ -1,6 +1,10 @@
 module SharedGroup
   include Spinach::DSL
 
+  step 'current user is developer of group "Owned"' do
+    is_member_of(current_user.name, "Owned", Gitlab::Access::DEVELOPER)
+  end
+
   step '"John Doe" is owner of group "Owned"' do
     is_member_of("John Doe", "Owned", Gitlab::Access::OWNER)
   end
@@ -41,4 +45,8 @@ module SharedGroup
     project.team << [user, :master]
     @project_count += 1
   end
+
+  def owned_group
+    @owned_group ||= Group.find_by(name: "Owned")
+  end
 end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index c74a5fd3bc7e7cfa7d044ccb3182622847b0baa6..b33bd332655849909229518a722539a9323cc23c 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -212,8 +212,8 @@ module SharedPaths
   end
 
   step 'I visit a binary file in the repo' do
-    visit namespace_project_blob_path(@project.namespace, @project, File.join(
-      root_ref, 'files/images/logo-black.png'))
+    visit namespace_project_blob_path(@project.namespace, @project,
+      File.join(root_ref, 'files/images/logo-black.png'))
   end
 
   step "I visit my project's commits page" do
@@ -316,8 +316,8 @@ module SharedPaths
   end
 
   step 'I am on the ".gitignore" edit file page' do
-    expect(current_path).to eq(namespace_project_edit_blob_path(
-      @project.namespace, @project, File.join(root_ref, '.gitignore')))
+    expect(current_path).to eq(
+      namespace_project_edit_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore')))
   end
 
   step 'I visit project source page for "6d39438"' do
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index 7021fac5fe4b4c5ab0b0548aa4cf16f921bb75e4..da643bf3ba964950a38753fa81d7a333bb1a4af4 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -204,7 +204,7 @@ module SharedProject
 
   step 'project "Shop" has CI build' do
     project = Project.find_by(name: "Shop")
-    create :ci_commit, gl_project: project, sha: project.commit.sha
+    create :ci_commit, project: project, sha: project.commit.sha
   end
 
   step 'I should see last commit with CI status' do
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index 33ff7084e30df9f45eb6dc46846f0d940918a4f9..4fc2ece79ff927ba2e526490126c8b4e6d8f251f 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -16,10 +16,6 @@ module SharedProjectTab
     ensure_active_main_tab('Commits')
   end
 
-  step 'the active main tab should be Network' do
-    ensure_active_main_tab('Network')
-  end
-
   step 'the active main tab should be Graphs' do
     ensure_active_main_tab('Graphs')
   end
@@ -53,4 +49,8 @@ module SharedProjectTab
   step 'the active main tab should be Activity' do
     ensure_active_main_tab('Activity')
   end
+
+  step 'the active sub tab should be Network' do
+    ensure_active_sub_tab('Network')
+  end
 end
diff --git a/features/steps/shared/user.rb b/features/steps/shared/user.rb
index fc1e8d6e88967cdef197ecd74de8d64d59a256a6..f0721094ee3f2cc09588e30c0e8a8d8517421b76 100644
--- a/features/steps/shared/user.rb
+++ b/features/steps/shared/user.rb
@@ -9,9 +9,21 @@ module SharedUser
     user_exists("Mary Jane", { username: "mary_jane" })
   end
 
+  step 'gitlab user "Mike"' do
+    create(:user, name: "Mike")
+  end
+
   protected
 
   def user_exists(name, options = {})
     User.find_by(name: name) || create(:user, { name: name, admin: false }.merge(options))
   end
+
+  step 'I have an ssh key' do
+    create(:personal_key, user: @user)
+  end
+
+  step 'I have no ssh keys' do
+    @user.keys.delete_all
+  end
 end
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
index 80d1ddeef055177d7df0d3ef3e2e46843e2a3609..023032e679f219001de31470cb25f97410b823a9 100644
--- a/features/steps/snippets/snippets.rb
+++ b/features/steps/snippets/snippets.rb
@@ -13,7 +13,7 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
   end
 
   step 'I click link "Edit"' do
-    page.within ".page-title" do
+    page.within ".detail-page-header" do
       click_link "Edit"
     end
   end
diff --git a/features/support/capybara.rb b/features/support/capybara.rb
index 31dbf0feb2fc57897ad34de32426bc7e8ae65e31..4156c7ec484611624902b615884bb19e962312be 100644
--- a/features/support/capybara.rb
+++ b/features/support/capybara.rb
@@ -2,7 +2,7 @@ require 'spinach/capybara'
 require 'capybara/poltergeist'
 
 # Give CI some extra time
-timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 10
+timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 15
 
 Capybara.javascript_driver = :poltergeist
 Capybara.register_driver :poltergeist do |app|
diff --git a/fixtures/emojis/aliases.json b/fixtures/emojis/aliases.json
new file mode 100644
index 0000000000000000000000000000000000000000..547ce7978b389ca4a5273cdbdbbcabb9bb42d275
--- /dev/null
+++ b/fixtures/emojis/aliases.json
@@ -0,0 +1,367 @@
+{  
+   "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",
+   "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",
+   "beach_with_umbrella":"beach",
+   "bellhop_bell":"bellhop",
+   "bouquet_of_flowers":"bouquet2",
+   "bullhorn_with_sound_waves":"bullhorn_waves",
+   "pocket calculator":"calculator",
+   "spiral_calendar_pad":"calendar_spiral",
+   "card_file_box":"card_box",
+   "tape_cartridge":"cartridge",
+   "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",
+   "couch_and_lamp":"couch",
+   "couple_with_heart_mm":"couple_mm",
+   "couple_with_heart_ww":"couple_ww",
+   "lower_left_crayon":"crayon",
+   "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",
+   "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",
+   "flame":"fire",
+   "oncoming_fire_engine":"fire_engine_oncoming",
+   "ac":"flag_ac",
+   "ad":"flag_ad",
+   "ae":"flag_ae",
+   "af":"flag_af",
+   "ag":"flag_ag",
+   "ai":"flag_ai",
+   "al":"flag_al",
+   "am":"flag_am",
+   "ao":"flag_ao",
+   "ar":"flag_ar",
+   "at":"flag_at",
+   "au":"flag_au",
+   "aw":"flag_aw",
+   "az":"flag_az",
+   "ba":"flag_ba",
+   "bb":"flag_bb",
+   "bd":"flag_bd",
+   "be":"flag_be",
+   "bf":"flag_bf",
+   "bg":"flag_bg",
+   "bh":"flag_bh",
+   "bi":"flag_bi",
+   "bj":"flag_bj",
+   "waving_black_flag":"flag_black",
+   "bm":"flag_bm",
+   "bn":"flag_bn",
+   "bo":"flag_bo",
+   "br":"flag_br",
+   "bs":"flag_bs",
+   "bt":"flag_bt",
+   "bw":"flag_bw",
+   "by":"flag_by",
+   "bz":"flag_bz",
+   "ca":"flag_ca",
+   "congo":"flag_cd",
+   "cf":"flag_cf",
+   "cg":"flag_cg",
+   "ch":"flag_ch",
+   "ci":"flag_ci",
+   "chile":"flag_cl",
+   "cm":"flag_cm",
+   "cn":"flag_cn",
+   "co":"flag_co",
+   "cr":"flag_cr",
+   "cu":"flag_cu",
+   "cv":"flag_cv",
+   "cy":"flag_cy",
+   "cz":"flag_cz",
+   "de":"flag_de",
+   "dj":"flag_dj",
+   "dk":"flag_dk",
+   "dm":"flag_dm",
+   "do":"flag_do",
+   "dz":"flag_dz",
+   "ec":"flag_ec",
+   "ee":"flag_ee",
+   "eg":"flag_eg",
+   "eh":"flag_eh",
+   "er":"flag_er",
+   "es":"flag_es",
+   "et":"flag_et",
+   "fi":"flag_fi",
+   "fj":"flag_fj",
+   "fk":"flag_fk",
+   "fm":"flag_fm",
+   "fo":"flag_fo",
+   "fr":"flag_fr",
+   "ga":"flag_ga",
+   "gb":"flag_gb",
+   "gd":"flag_gd",
+   "ge":"flag_ge",
+   "gh":"flag_gh",
+   "gi":"flag_gi",
+   "gl":"flag_gl",
+   "gm":"flag_gm",
+   "gn":"flag_gn",
+   "gq":"flag_gq",
+   "gr":"flag_gr",
+   "gt":"flag_gt",
+   "gu":"flag_gu",
+   "gw":"flag_gw",
+   "gy":"flag_gy",
+   "hk":"flag_hk",
+   "hn":"flag_hn",
+   "hr":"flag_hr",
+   "ht":"flag_ht",
+   "hu":"flag_hu",
+   "indonesia":"flag_id",
+   "ie":"flag_ie",
+   "il":"flag_il",
+   "in":"flag_in",
+   "iq":"flag_iq",
+   "ir":"flag_ir",
+   "is":"flag_is",
+   "it":"flag_it",
+   "je":"flag_je",
+   "jm":"flag_jm",
+   "jo":"flag_jo",
+   "jp":"flag_jp",
+   "ke":"flag_ke",
+   "kg":"flag_kg",
+   "kh":"flag_kh",
+   "ki":"flag_ki",
+   "km":"flag_km",
+   "kn":"flag_kn",
+   "kp":"flag_kp",
+   "kr":"flag_kr",
+   "kw":"flag_kw",
+   "ky":"flag_ky",
+   "kz":"flag_kz",
+   "la":"flag_la",
+   "lb":"flag_lb",
+   "lc":"flag_lc",
+   "li":"flag_li",
+   "lk":"flag_lk",
+   "lr":"flag_lr",
+   "ls":"flag_ls",
+   "lt":"flag_lt",
+   "lu":"flag_lu",
+   "lv":"flag_lv",
+   "ly":"flag_ly",
+   "ma":"flag_ma",
+   "mc":"flag_mc",
+   "md":"flag_md",
+   "me":"flag_me",
+   "mg":"flag_mg",
+   "mh":"flag_mh",
+   "mk":"flag_mk",
+   "ml":"flag_ml",
+   "mm":"flag_mm",
+   "mn":"flag_mn",
+   "mo":"flag_mo",
+   "mr":"flag_mr",
+   "ms":"flag_ms",
+   "mt":"flag_mt",
+   "mu":"flag_mu",
+   "mv":"flag_mv",
+   "mw":"flag_mw",
+   "mx":"flag_mx",
+   "my":"flag_my",
+   "mz":"flag_mz",
+   "na":"flag_na",
+   "nc":"flag_nc",
+   "ne":"flag_ne",
+   "nigeria":"flag_ng",
+   "ni":"flag_ni",
+   "nl":"flag_nl",
+   "no":"flag_no",
+   "np":"flag_np",
+   "nr":"flag_nr",
+   "nu":"flag_nu",
+   "nz":"flag_nz",
+   "om":"flag_om",
+   "pa":"flag_pa",
+   "pe":"flag_pe",
+   "pf":"flag_pf",
+   "pg":"flag_pg",
+   "ph":"flag_ph",
+   "pk":"flag_pk",
+   "pl":"flag_pl",
+   "pr":"flag_pr",
+   "ps":"flag_ps",
+   "pt":"flag_pt",
+   "pw":"flag_pw",
+   "py":"flag_py",
+   "qa":"flag_qa",
+   "ro":"flag_ro",
+   "rs":"flag_rs",
+   "ru":"flag_ru",
+   "rw":"flag_rw",
+   "saudiarabia":"flag_sa",
+   "saudi":"flag_sa",
+   "sb":"flag_sb",
+   "sc":"flag_sc",
+   "sd":"flag_sd",
+   "se":"flag_se",
+   "sg":"flag_sg",
+   "sh":"flag_sh",
+   "si":"flag_si",
+   "sk":"flag_sk",
+   "sl":"flag_sl",
+   "sm":"flag_sm",
+   "sn":"flag_sn",
+   "so":"flag_so",
+   "sr":"flag_sr",
+   "st":"flag_st",
+   "sv":"flag_sv",
+   "sy":"flag_sy",
+   "sz":"flag_sz",
+   "td":"flag_td",
+   "tg":"flag_tg",
+   "th":"flag_th",
+   "tj":"flag_tj",
+   "tl":"flag_tl",
+   "turkmenistan":"flag_tm",
+   "tn":"flag_tn",
+   "to":"flag_to",
+   "tr":"flag_tr",
+   "tt":"flag_tt",
+   "tuvalu":"flag_tv",
+   "tw":"flag_tw",
+   "tz":"flag_tz",
+   "ua":"flag_ua",
+   "ug":"flag_ug",
+   "us":"flag_us",
+   "uy":"flag_uy",
+   "uz":"flag_uz",
+   "va":"flag_va",
+   "vc":"flag_vc",
+   "ve":"flag_ve",
+   "vi":"flag_vi",
+   "vn":"flag_vn",
+   "vu":"flag_vu",
+   "wf":"flag_wf",
+   "waving_white_flag":"flag_white",
+   "ws":"flag_ws",
+   "xk":"flag_xk",
+   "ye":"flag_ye",
+   "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",
+   "frame_with_picture":"frame_photo",
+   "frame_with_tiles":"frame_tiles",
+   "frame_with_an_x":"frame_x",
+   "anguished":"frowning",
+   "raised_hand_with_fingers_splayed":"hand_splayed",
+   "reversed_raised_hand_with_fingers_splayed":"hand_splayed_reverse",
+   "reversed_victory_hand":"hand_victory",
+   "heart_with_tip_on_the_left":"heart_tip",
+   "house_buildings":"homes",
+   "derelict_house_building":"house_abandoned",
+   "circled_information_source":"info",
+   "desert_island":"island",
+   "up_pointing_military_airplane":"jet_up",
+   "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",
+   "satisfied":"laughing",
+   "left_hand_telephone_receiver":"left_receiver",
+   "man_in_business_suit_levitating":"levitate",
+   "weight_lifter":"lifter",
+   "light_mark":"light_check_mark",
+   "world_map":"map",
+   "sports_medal":"medal",
+   "studio_microphone":"microphone2",
+   "reversed_hand_with_middle_finger_extended":"middle_finger",
+   "lightning_mood_bubble":"mood_bubble_lightning",
+   "lightning_mood":"mood_lightning",
+   "racing_motorcycle":"motorcycle",
+   "snow_capped_mountain":"mountain_snow",
+   "one_button_mouse":"mouse_one",
+   "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",
+   "oil_drum":"oil",
+   "grandma":"older_woman",
+   "optical_disc_icon":"optical_disk",
+   "lower_left_paintbrush":"paintbrush",
+   "linked_paperclips":"paperclips",
+   "national_park":"park",
+   "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",
+   "no_piracy":"piracy",
+   "shit":"poop",
+   "hankey":"poop",
+   "poo":"poop",
+   "prohibited_sign":"prohibited",
+   "film_projector":"projector",
+   "racing_car":"race_car",
+   "railroad_track":"railway_track",
+   "right_speaker_with_one_sound_wave":"right_speaker_one",
+   "right_speaker_with_three_sound_waves":"right_speaker_three",
+   "skeleton":"skull",
+   "slightly_frowning_face":"slight_frown",
+   "slightly_smiling_face":"slight_smile",
+   "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",
+   "portable_stereo":"stereo",
+   "black_touchtone_telephone":"telephone_black",
+   "white_touchtone_telephone":"telephone_white",
+   "left_thought_bubble":"thought_left",
+   "right_thought_bubble":"thought_right",
+   "reversed_thumbs_down_sign":"thumbs_down_reverse",
+   "reversed_thumbs_up_sign":"thumbs_up_reverse",
+   "-1":"thumbsdown",
+   "+1":"thumbsup",
+   "admission_tickets":"tickets",
+   "hammer_and_wrench":"tools",
+   "diesel_locomotive":"train_diesel",
+   "triangle_with_rounded_corners":"triangle_round",
+   "turned_ok_hand_sign":"turned_ok_hand",
+   "raised_hand_with_part_between_middle_and_ring_fingers":"vulcan",
+   "left_writing_hand":"writing_hand"
+}
\ No newline at end of file
diff --git a/fixtures/emojis/index.json b/fixtures/emojis/index.json
new file mode 100644
index 0000000000000000000000000000000000000000..60ef2399e14de6b711aa4a5e0b92ce97ab1e791a
--- /dev/null
+++ b/fixtures/emojis/index.json
@@ -0,0 +1,13376 @@
+{
+  "100": {
+    "unicode": "1F4AF",
+    "unicode_alternates": [],
+    "name": "hundred points symbol",
+    "shortname": ":100:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["numbers", "perfect", "score", "100", "percent", "a", "plus", "perfect", "school", "quiz", "score", "test", "exam"],
+    "moji": "💯"
+  },
+  "1234": {
+    "unicode": "1F522",
+    "unicode_alternates": [],
+    "name": "input symbol for numbers",
+    "shortname": ":1234:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "numbers"],
+    "moji": "🔢"
+  },
+  "8ball": {
+    "unicode": "1F3B1",
+    "unicode_alternates": [],
+    "name": "billiards",
+    "shortname": ":8ball:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["pool", "billiards", "eight ball", "pool", "pocket ball", "cue"],
+    "moji": "🎱"
+  },
+  "a": {
+    "unicode": "1F170",
+    "unicode_alternates": [],
+    "name": "negative squared latin capital letter a",
+    "shortname": ":a:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alphabet", "letter", "red-square"],
+    "moji": "🅰"
+  },
+  "ab": {
+    "unicode": "1F18E",
+    "unicode_alternates": [],
+    "name": "negative squared ab",
+    "shortname": ":ab:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alphabet", "red-square"],
+    "moji": "🆎"
+  },
+  "abc": {
+    "unicode": "1F524",
+    "unicode_alternates": [],
+    "name": "input symbol for latin letters",
+    "shortname": ":abc:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alphabet", "blue-square"],
+    "moji": "🔤"
+  },
+  "abcd": {
+    "unicode": "1F521",
+    "unicode_alternates": [],
+    "name": "input symbol for latin small letters",
+    "shortname": ":abcd:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alphabet", "blue-square"],
+    "moji": "🔡"
+  },
+  "accept": {
+    "unicode": "1F251",
+    "unicode_alternates": [],
+    "name": "circled ideograph accept",
+    "shortname": ":accept:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["agree", "chinese", "good", "kanji", "ok", "yes"],
+    "moji": "🉑"
+  },
+  "aerial_tramway": {
+    "unicode": "1F6A1",
+    "unicode_alternates": [],
+    "name": "aerial tramway",
+    "shortname": ":aerial_tramway:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle", "aerial", "tram", "tramway", "cable", "transport"],
+    "moji": "🚡"
+  },
+  "airplane": {
+    "unicode": "2708",
+    "unicode_alternates": ["2708-FE0F"],
+    "name": "airplane",
+    "shortname": ":airplane:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["flight", "transportation", "vehicle", "airplane", "plane", "airport", "travel", "airlines", "fly", "jet", "jumbo", "boeing", "airbus"],
+    "moji": "✈"
+  },
+  "airplane_arriving": {
+    "unicode": "1F6EC",
+    "unicode_alternates": [],
+    "name": "airplane arriving",
+    "shortname": ":airplane_arriving:",
+    "category": "travel_places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["flight", "transportation", "vehicle", "plane", "airport", "travel", "airlines", "fly", "jet", "jumbo", "boeing", "airbus"]
+  },
+  "airplane_departure": {
+    "unicode": "1F6EB",
+    "unicode_alternates": [],
+    "name": "airplane departure",
+    "shortname": ":airplane_departure:",
+    "category": "travel_places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["flight", "transportation", "vehicle", "plane", "airport", "travel", "airlines", "fly", "jet", "jumbo", "boeing", "airbus", "leaving"]
+  },
+  "airplane_northeast": {
+    "unicode": "1F6EA",
+    "unicode_alternates": [],
+    "name": "northeast-pointing airplane",
+    "shortname": ":airplane_northeast:",
+    "category": "travel_places",
+    "aliases": [":northeast_pointing_airplane:"],
+    "aliases_ascii": [],
+    "keywords": ["plane", "travel"]
+  },
+  "airplane_small": {
+    "unicode": "1F6E9",
+    "unicode_alternates": [],
+    "name": "small airplane",
+    "shortname": ":airplane_small:",
+    "category": "travel_places",
+    "aliases": [":small_airplane:"],
+    "aliases_ascii": [],
+    "keywords": ["flight", "transportation", "vehicle", "plane", "airport", "travel", "airlines", "fly", "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:"],
+    "aliases_ascii": [],
+    "keywords": ["plane", "travel"]
+  },
+  "alarm_clock": {
+    "unicode": "23F0",
+    "unicode_alternates": [],
+    "name": "alarm clock",
+    "shortname": ":alarm_clock:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["time", "wake"],
+    "moji": "⏰"
+  },
+  "alien": {
+    "unicode": "1F47D",
+    "unicode_alternates": [],
+    "name": "extraterrestrial alien",
+    "shortname": ":alien:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["UFO", "paul", "alien", "ufo"],
+    "moji": "👽"
+  },
+  "ambulance": {
+    "unicode": "1F691",
+    "unicode_alternates": [],
+    "name": "ambulance",
+    "shortname": ":ambulance:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["911", "health", "ambulance", "emergency", "medical", "help", "assistance"],
+    "moji": "🚑"
+  },
+  "anchor": {
+    "unicode": "2693",
+    "unicode_alternates": ["2693-FE0F"],
+    "name": "anchor",
+    "shortname": ":anchor:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["ferry", "ship", "anchor", "ship", "boat", "ocean", "harbor", "marina", "shipyard", "sailor", "tattoo"],
+    "moji": "⚓"
+  },
+  "angel": {
+    "unicode": "1F47C",
+    "unicode_alternates": [],
+    "name": "baby angel",
+    "shortname": ":angel:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["baby", "angel", "halo", "cupid", "wings", "halo", "heaven", "wings", "jesus"],
+    "moji": "👼"
+  },
+  "anger": {
+    "unicode": "1F4A2",
+    "unicode_alternates": [],
+    "name": "anger symbol",
+    "shortname": ":anger:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["anger", "angry", "mad"],
+    "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",
+    "aliases": [":right_anger_bubble:"],
+    "aliases_ascii": [],
+    "keywords": ["speech", "balloon", "talk", "mood", "conversation", "communication", "comic", "angry"]
+  },
+  "angry": {
+    "unicode": "1F620",
+    "unicode_alternates": [],
+    "name": "angry face",
+    "shortname": ":angry:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [">:(", ">:-(", ":@"],
+    "keywords": ["angry", "livid", "mad", "vexed", "irritated", "annoyed", "face", "frustrated", "mad"],
+    "moji": "😠"
+  },
+  "anguished": {
+    "unicode": "1F627",
+    "unicode_alternates": [],
+    "name": "anguished face",
+    "shortname": ":anguished:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "nervous", "stunned", "pain", "anguish", "ouch", "misery", "distress", "grief"],
+    "moji": "😧"
+  },
+  "ant": {
+    "unicode": "1F41C",
+    "unicode_alternates": [],
+    "name": "ant",
+    "shortname": ":ant:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "insect", "ant", "queen", "insect", "team"],
+    "moji": "🐜"
+  },
+  "apple": {
+    "unicode": "1F34E",
+    "unicode_alternates": [],
+    "name": "red apple",
+    "shortname": ":apple:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fruit", "mac", "apple", "fruit", "electronics", "red", "doctor", "teacher", "school", "core"],
+    "moji": "🍎"
+  },
+  "aquarius": {
+    "unicode": "2652",
+    "unicode_alternates": ["2652-FE0F"],
+    "name": "aquarius",
+    "shortname": ":aquarius:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["aquarius", "water", "bearer", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+    "moji": "♒"
+  },
+  "aries": {
+    "unicode": "2648",
+    "unicode_alternates": ["2648-FE0F"],
+    "name": "aries",
+    "shortname": ":aries:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["aries", "ram", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+    "moji": "♈"
+  },
+  "arrow_backward": {
+    "unicode": "25C0",
+    "unicode_alternates": ["25C0-FE0F"],
+    "name": "black left-pointing triangle",
+    "shortname": ":arrow_backward:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "blue-square"],
+    "moji": "◀"
+  },
+  "arrow_double_down": {
+    "unicode": "23EC",
+    "unicode_alternates": [],
+    "name": "black down-pointing double triangle",
+    "shortname": ":arrow_double_down:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "blue-square"],
+    "moji": "⏬"
+  },
+  "arrow_double_up": {
+    "unicode": "23EB",
+    "unicode_alternates": [],
+    "name": "black up-pointing double triangle",
+    "shortname": ":arrow_double_up:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "blue-square"],
+    "moji": "⏫"
+  },
+  "arrow_down": {
+    "unicode": "2B07",
+    "unicode_alternates": ["2B07-FE0F"],
+    "name": "downwards black arrow",
+    "shortname": ":arrow_down:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "blue-square"],
+    "moji": "⬇"
+  },
+  "arrow_down_small": {
+    "unicode": "1F53D",
+    "unicode_alternates": [],
+    "name": "down-pointing small red triangle",
+    "shortname": ":arrow_down_small:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "blue-square"],
+    "moji": "🔽"
+  },
+  "arrow_forward": {
+    "unicode": "25B6",
+    "unicode_alternates": ["25B6-FE0F"],
+    "name": "black right-pointing triangle",
+    "shortname": ":arrow_forward:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "blue-square"],
+    "moji": "▶"
+  },
+  "arrow_heading_down": {
+    "unicode": "2935",
+    "unicode_alternates": ["2935-FE0F"],
+    "name": "arrow pointing rightwards then curving downwards",
+    "shortname": ":arrow_heading_down:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "blue-square"],
+    "moji": "⤵"
+  },
+  "arrow_heading_up": {
+    "unicode": "2934",
+    "unicode_alternates": ["2934-FE0F"],
+    "name": "arrow pointing rightwards then curving upwards",
+    "shortname": ":arrow_heading_up:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "blue-square"],
+    "moji": "⤴"
+  },
+  "arrow_left": {
+    "unicode": "2B05",
+    "unicode_alternates": ["2B05-FE0F"],
+    "name": "leftwards black arrow",
+    "shortname": ":arrow_left:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "blue-square", "previous"],
+    "moji": "⬅"
+  },
+  "arrow_lower_left": {
+    "unicode": "2199",
+    "unicode_alternates": ["2199-FE0F"],
+    "name": "south west arrow",
+    "shortname": ":arrow_lower_left:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "blue-square"],
+    "moji": "↙"
+  },
+  "arrow_lower_right": {
+    "unicode": "2198",
+    "unicode_alternates": ["2198-FE0F"],
+    "name": "south east arrow",
+    "shortname": ":arrow_lower_right:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "blue-square"],
+    "moji": "↘"
+  },
+  "arrow_right": {
+    "unicode": "27A1",
+    "unicode_alternates": ["27A1-FE0F"],
+    "name": "black rightwards arrow",
+    "shortname": ":arrow_right:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "next"],
+    "moji": "➡"
+  },
+  "arrow_right_hook": {
+    "unicode": "21AA",
+    "unicode_alternates": ["21AA-FE0F"],
+    "name": "rightwards arrow with hook",
+    "shortname": ":arrow_right_hook:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square"],
+    "moji": "↪"
+  },
+  "arrow_up": {
+    "unicode": "2B06",
+    "unicode_alternates": ["2B06-FE0F"],
+    "name": "upwards black arrow",
+    "shortname": ":arrow_up:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square"],
+    "moji": "⬆"
+  },
+  "arrow_up_down": {
+    "unicode": "2195",
+    "unicode_alternates": ["2195-FE0F"],
+    "name": "up down arrow",
+    "shortname": ":arrow_up_down:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square"],
+    "moji": "↕"
+  },
+  "arrow_up_small": {
+    "unicode": "1F53C",
+    "unicode_alternates": [],
+    "name": "up-pointing small red triangle",
+    "shortname": ":arrow_up_small:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square"],
+    "moji": "🔼"
+  },
+  "arrow_upper_left": {
+    "unicode": "2196",
+    "unicode_alternates": ["2196-FE0F"],
+    "name": "north west arrow",
+    "shortname": ":arrow_upper_left:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square"],
+    "moji": "↖"
+  },
+  "arrow_upper_right": {
+    "unicode": "2197",
+    "unicode_alternates": ["2197-FE0F"],
+    "name": "north east arrow",
+    "shortname": ":arrow_upper_right:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square"],
+    "moji": "↗"
+  },
+  "arrows_clockwise": {
+    "unicode": "1F503",
+    "unicode_alternates": [],
+    "name": "clockwise downwards and upwards open circle arrows",
+    "shortname": ":arrows_clockwise:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sync"],
+    "moji": "🔃"
+  },
+  "arrows_counterclockwise": {
+    "unicode": "1F504",
+    "unicode_alternates": [],
+    "name": "anticlockwise downwards and upwards open circle ar",
+    "shortname": ":arrows_counterclockwise:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "sync"],
+    "moji": "🔄"
+  },
+  "art": {
+    "unicode": "1F3A8",
+    "unicode_alternates": [],
+    "name": "artist palette",
+    "shortname": ":art:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["design", "draw", "paint", "artist", "palette", "art", "colors", "paint", "draw", "brush", "pastels", "oils"],
+    "moji": "🎨"
+  },
+  "articulated_lorry": {
+    "unicode": "1F69B",
+    "unicode_alternates": [],
+    "name": "articulated lorry",
+    "shortname": ":articulated_lorry:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cars", "transportation", "vehicle", "truck", "delivery", "semi", "lorry", "articulated"],
+    "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"]
+  },
+  "astonished": {
+    "unicode": "1F632",
+    "unicode_alternates": [],
+    "name": "astonished face",
+    "shortname": ":astonished:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "xox", "shocked", "surprise", "astonished"],
+    "moji": "😲"
+  },
+  "athletic_shoe": {
+    "unicode": "1F45F",
+    "unicode_alternates": [],
+    "name": "athletic shoe",
+    "shortname": ":athletic_shoe:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shoes", "sports"],
+    "moji": "👟"
+  },
+  "atm": {
+    "unicode": "1F3E7",
+    "unicode_alternates": [],
+    "name": "automated teller machine",
+    "shortname": ":atm:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["atm", "cash", "withdrawal", "money", "deposit", "financial", "bank", "adam", "payday", "bank", "blue-square", "cash", "money", "payment"],
+    "moji": "🏧"
+  },
+  "b": {
+    "unicode": "1F171",
+    "unicode_alternates": [],
+    "name": "negative squared latin capital letter b",
+    "shortname": ":b:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alphabet", "letter", "red-square"],
+    "moji": "🅱"
+  },
+  "baby": {
+    "unicode": "1F476",
+    "unicode_alternates": [],
+    "name": "baby",
+    "shortname": ":baby:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["boy", "child", "infant"],
+    "moji": "👶"
+  },
+  "baby_bottle": {
+    "unicode": "1F37C",
+    "unicode_alternates": [],
+    "name": "baby bottle",
+    "shortname": ":baby_bottle:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["container", "food", "baby", "bottle", "milk", "mother", "nipple", "newborn", "formula"],
+    "moji": "🍼"
+  },
+  "baby_chick": {
+    "unicode": "1F424",
+    "unicode_alternates": [],
+    "name": "baby chick",
+    "shortname": ":baby_chick:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "chicken", "chick", "baby", "bird", "chicken", "young", "woman", "cute"],
+    "moji": "🐤"
+  },
+  "baby_symbol": {
+    "unicode": "1F6BC",
+    "unicode_alternates": [],
+    "name": "baby symbol",
+    "shortname": ":baby_symbol:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["child", "orange-square", "baby", "crawl", "newborn", "human", "diaper", "small", "babe"],
+    "moji": "🚼"
+  },
+  "back": {
+    "unicode": "1F519",
+    "unicode_alternates": [],
+    "name": "back with leftwards arrow above",
+    "shortname": ":back:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow"],
+    "moji": "🔙"
+  },
+  "baggage_claim": {
+    "unicode": "1F6C4",
+    "unicode_alternates": [],
+    "name": "baggage claim",
+    "shortname": ":baggage_claim:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["airport", "blue-square", "transport", "bag", "baggage", "luggage", "travel"],
+    "moji": "🛄"
+  },
+  "balloon": {
+    "unicode": "1F388",
+    "unicode_alternates": [],
+    "name": "balloon",
+    "shortname": ":balloon:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["celebration", "party", "balloon", "birthday", "celebration", "helium", "gas", "children", "float"],
+    "moji": "🎈"
+  },
+  "ballot_box": {
+    "unicode": "1F5F3",
+    "unicode_alternates": [],
+    "name": "ballot box with ballot",
+    "shortname": ":ballot_box:",
+    "category": "objects_symbols",
+    "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:"],
+    "aliases_ascii": [],
+    "keywords": ["mark", "vote"]
+  },
+  "ballot_box_with_check": {
+    "unicode": "2611",
+    "unicode_alternates": ["2611-FE0F"],
+    "name": "ballot box with check",
+    "shortname": ":ballot_box_with_check:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["agree", "ok"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "plant", "vegetable", "pine", "bamboo", "decoration", "new", "years", "spirits", "harvest", "prosperity", "longevity", "fortune", "luck", "welcome", "farming", "agriculture"],
+    "moji": "🎍"
+  },
+  "banana": {
+    "unicode": "1F34C",
+    "unicode_alternates": [],
+    "name": "banana",
+    "shortname": ":banana:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "fruit", "banana", "peel", "bunch"],
+    "moji": "🍌"
+  },
+  "bangbang": {
+    "unicode": "203C",
+    "unicode_alternates": ["203C-FE0F"],
+    "name": "double exclamation mark",
+    "shortname": ":bangbang:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["exclamation", "surprise"],
+    "moji": "‼"
+  },
+  "bank": {
+    "unicode": "1F3E6",
+    "unicode_alternates": [],
+    "name": "bank",
+    "shortname": ":bank:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building"],
+    "moji": "🏦"
+  },
+  "bar_chart": {
+    "unicode": "1F4CA",
+    "unicode_alternates": [],
+    "name": "bar chart",
+    "shortname": ":bar_chart:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["graph", "presentation", "stats"],
+    "moji": "📊"
+  },
+  "barber": {
+    "unicode": "1F488",
+    "unicode_alternates": [],
+    "name": "barber pole",
+    "shortname": ":barber:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["hair", "salon", "style"],
+    "moji": "💈"
+  },
+  "baseball": {
+    "unicode": "26BE",
+    "unicode_alternates": ["26BE-FE0F"],
+    "name": "baseball",
+    "shortname": ":baseball:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["MLB", "balls", "sports"],
+    "moji": "⚾"
+  },
+  "basketball": {
+    "unicode": "1F3C0",
+    "unicode_alternates": [],
+    "name": "basketball and hoop",
+    "shortname": ":basketball:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["NBA", "balls", "sports", "basketball", "bball", "dribble", "hoop", "net", "swish", "rip city"],
+    "moji": "🏀"
+  },
+  "bath": {
+    "unicode": "1F6C0",
+    "unicode_alternates": [],
+    "name": "bath",
+    "shortname": ":bath:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clean", "shower", "bath", "tub", "basin", "wash", "bubble", "soak", "bathroom", "soap", "water", "clean", "shampoo", "lather", "water"],
+    "moji": "🛀"
+  },
+  "bathtub": {
+    "unicode": "1F6C1",
+    "unicode_alternates": [],
+    "name": "bathtub",
+    "shortname": ":bathtub:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clean", "shower", "bath", "tub", "basin", "wash", "bubble", "soak", "bathroom", "soap", "water", "clean", "shampoo", "lather", "water"],
+    "moji": "🛁"
+  },
+  "battery": {
+    "unicode": "1F50B",
+    "unicode_alternates": [],
+    "name": "battery",
+    "shortname": ":battery:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["energy", "power", "sustain"],
+    "moji": "🔋"
+  },
+  "beach": {
+    "unicode": "1F3D6",
+    "unicode_alternates": [],
+    "name": "beach with umbrella",
+    "shortname": ":beach:",
+    "category": "travel_places",
+    "aliases": [":beach_with_umbrella:"],
+    "aliases_ascii": [],
+    "keywords": ["sand", "sun", "surf", "vacation", "relaxation", "tanning", "tan", "swimming"]
+  },
+  "bear": {
+    "unicode": "1F43B",
+    "unicode_alternates": [],
+    "name": "bear face",
+    "shortname": ":bear:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature"],
+    "moji": "🐻"
+  },
+  "bed": {
+    "unicode": "1F6CF",
+    "unicode_alternates": [],
+    "name": "bed",
+    "shortname": ":bed:",
+    "category": "travel_places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sleep", "sex", "queen", "full", "twin", "king", "mattress"]
+  },
+  "bee": {
+    "unicode": "1F41D",
+    "unicode_alternates": [],
+    "name": "honeybee",
+    "shortname": ":bee:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "insect", "bee", "queen", "buzz", "flower", "pollen", "sting", "honey", "hive", "bumble", "pollination"],
+    "moji": "🐝"
+  },
+  "beer": {
+    "unicode": "1F37A",
+    "unicode_alternates": [],
+    "name": "beer mug",
+    "shortname": ":beer:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["beverage", "drink", "drunk", "party", "pub", "relax", "beer", "hops", "mug", "barley", "malt", "yeast", "portland", "oregon", "brewery", "micro", "pint", "boot"],
+    "moji": "🍺"
+  },
+  "beers": {
+    "unicode": "1F37B",
+    "unicode_alternates": [],
+    "name": "clinking beer mugs",
+    "shortname": ":beers:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["beverage", "drink", "drunk", "party", "pub", "relax", "beer", "beers", "cheers", "mug", "toast", "celebrate", "pub", "bar", "jolly", "hops", "clink"],
+    "moji": "🍻"
+  },
+  "beetle": {
+    "unicode": "1F41E",
+    "unicode_alternates": [],
+    "name": "lady beetle",
+    "shortname": ":beetle:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["insect", "nature", "lady", "bug", "ladybug", "ladybird", "beetle", "cow", "lady cow", "insect", "endearment"],
+    "moji": "🐞"
+  },
+  "beginner": {
+    "unicode": "1F530",
+    "unicode_alternates": [],
+    "name": "japanese symbol for beginner",
+    "shortname": ":beginner:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["badge", "shield"],
+    "moji": "🔰"
+  },
+  "bell": {
+    "unicode": "1F514",
+    "unicode_alternates": [],
+    "name": "bell",
+    "shortname": ":bell:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chime", "christmas", "notification", "sound", "xmas"],
+    "moji": "🔔"
+  },
+  "bellhop": {
+    "unicode": "1F6CE",
+    "unicode_alternates": [],
+    "name": "bellhop bell",
+    "shortname": ":bellhop:",
+    "category": "travel_places",
+    "aliases": [":bellhop_bell:"],
+    "aliases_ascii": [],
+    "keywords": ["hotel", "porter", "ding"]
+  },
+  "bento": {
+    "unicode": "1F371",
+    "unicode_alternates": [],
+    "name": "bento box",
+    "shortname": ":bento:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["box", "food", "japanese", "bento", "japanese", "rice", "meal", "box", "obento", "convenient", "lunchbox"],
+    "moji": "🍱"
+  },
+  "bicyclist": {
+    "unicode": "1F6B4",
+    "unicode_alternates": [],
+    "name": "bicyclist",
+    "shortname": ":bicyclist:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bike", "exercise", "hipster", "sports", "bicyclist", "road", "bike", "pedal", "bicycle", "transportation"],
+    "moji": "🚴"
+  },
+  "bike": {
+    "unicode": "1F6B2",
+    "unicode_alternates": [],
+    "name": "bicycle",
+    "shortname": ":bike:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bicycle", "exercise", "hipster", "sports", "bike", "pedal", "bicycle", "transportation"],
+    "moji": "🚲"
+  },
+  "bikini": {
+    "unicode": "1F459",
+    "unicode_alternates": [],
+    "name": "bikini",
+    "shortname": ":bikini:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["beach", "fashion", "female", "girl", "swimming", "woman"],
+    "moji": "👙"
+  },
+  "bird": {
+    "unicode": "1F426",
+    "unicode_alternates": [],
+    "name": "bird",
+    "shortname": ":bird:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "fly", "nature", "tweet"],
+    "moji": "🐦"
+  },
+  "birthday": {
+    "unicode": "1F382",
+    "unicode_alternates": [],
+    "name": "birthday cake",
+    "shortname": ":birthday:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cake", "party", "birthday", "birth", "cake", "dessert", "wish", "celebrate"],
+    "moji": "🎂"
+  },
+  "black_circle": {
+    "unicode": "26AB",
+    "unicode_alternates": ["26AB-FE0F"],
+    "name": "medium black circle",
+    "shortname": ":black_circle:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "⚫"
+  },
+  "black_joker": {
+    "unicode": "1F0CF",
+    "unicode_alternates": [],
+    "name": "playing card black joker",
+    "shortname": ":black_joker:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cards", "game", "poker"],
+    "moji": "🃏"
+  },
+  "black_large_square": {
+    "unicode": "2B1B",
+    "unicode_alternates": ["2B1B-FE0F"],
+    "name": "black large square",
+    "shortname": ":black_large_square:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "⬛"
+  },
+  "black_medium_small_square": {
+    "unicode": "25FE",
+    "unicode_alternates": ["25FE-FE0F"],
+    "name": "black medium small square",
+    "shortname": ":black_medium_small_square:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "◾"
+  },
+  "black_medium_square": {
+    "unicode": "25FC",
+    "unicode_alternates": ["25FC-FE0F"],
+    "name": "black medium square",
+    "shortname": ":black_medium_square:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "◼"
+  },
+  "black_nib": {
+    "unicode": "2712",
+    "unicode_alternates": ["2712-FE0F"],
+    "name": "black nib",
+    "shortname": ":black_nib:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["pen", "stationery"],
+    "moji": "✒"
+  },
+  "black_small_square": {
+    "unicode": "25AA",
+    "unicode_alternates": ["25AA-FE0F"],
+    "name": "black small square",
+    "shortname": ":black_small_square:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "▪"
+  },
+  "black_square_button": {
+    "unicode": "1F532",
+    "unicode_alternates": [],
+    "name": "black square button",
+    "shortname": ":black_square_button:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["frame"],
+    "moji": "🔲"
+  },
+  "blossom": {
+    "unicode": "1F33C",
+    "unicode_alternates": [],
+    "name": "blossom",
+    "shortname": ":blossom:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["flowers", "nature", "yellow", "blossom", "daisy", "flower"],
+    "moji": "🌼"
+  },
+  "blowfish": {
+    "unicode": "1F421",
+    "unicode_alternates": [],
+    "name": "blowfish",
+    "shortname": ":blowfish:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "nature", "ocean", "sea", "blowfish", "pufferfish", "puffer", "ballonfish", "toadfish", "fugu fish", "sushi"],
+    "moji": "🐡"
+  },
+  "blue_book": {
+    "unicode": "1F4D8",
+    "unicode_alternates": [],
+    "name": "blue book",
+    "shortname": ":blue_book:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["knowledge", "library", "read"],
+    "moji": "📘"
+  },
+  "blue_car": {
+    "unicode": "1F699",
+    "unicode_alternates": [],
+    "name": "recreational vehicle",
+    "shortname": ":blue_car:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["car", "suv", "car", "wagon", "automobile"],
+    "moji": "🚙"
+  },
+  "blue_heart": {
+    "unicode": "1F499",
+    "unicode_alternates": [],
+    "name": "blue heart",
+    "shortname": ":blue_heart:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "like", "love", "valentines", "blue", "heart", "love", "stability", "truth", "loyalty", "trust"],
+    "moji": "💙"
+  },
+  "blush": {
+    "unicode": "1F60A",
+    "unicode_alternates": [],
+    "name": "smiling face with smiling eyes",
+    "shortname": ":blush:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["crush", "embarrassed", "face", "flushed", "happy", "shy", "smile", "smiling", "smile", "smiley"],
+    "moji": "😊"
+  },
+  "boar": {
+    "unicode": "1F417",
+    "unicode_alternates": [],
+    "name": "boar",
+    "shortname": ":boar:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature"],
+    "moji": "🐗"
+  },
+  "bomb": {
+    "unicode": "1F4A3",
+    "unicode_alternates": [],
+    "name": "bomb",
+    "shortname": ":bomb:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["boom", "explode"],
+    "moji": "💣"
+  },
+  "book": {
+    "unicode": "1F4D6",
+    "unicode_alternates": [],
+    "name": "open book",
+    "shortname": ":book:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["library", "literature"],
+    "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": [],
+    "name": "bookmark",
+    "shortname": ":bookmark:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["favorite"],
+    "moji": "🔖"
+  },
+  "bookmark_tabs": {
+    "unicode": "1F4D1",
+    "unicode_alternates": [],
+    "name": "bookmark tabs",
+    "shortname": ":bookmark_tabs:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["favorite"],
+    "moji": "📑"
+  },
+  "books": {
+    "unicode": "1F4DA",
+    "unicode_alternates": [],
+    "name": "books",
+    "shortname": ":books:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["library", "literature"],
+    "moji": "📚"
+  },
+  "boom": {
+    "unicode": "1F4A5",
+    "unicode_alternates": [],
+    "name": "collision symbol",
+    "shortname": ":boom:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bomb", "explode", "explosion", "boom", "bang", "collision", "fire", "emphasis", "wow", "bam"],
+    "moji": "💥"
+  },
+  "boot": {
+    "unicode": "1F462",
+    "unicode_alternates": [],
+    "name": "womans boots",
+    "shortname": ":boot:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fashion", "shoes"],
+    "moji": "👢"
+  },
+  "bouquet": {
+    "unicode": "1F490",
+    "unicode_alternates": [],
+    "name": "bouquet",
+    "shortname": ":bouquet:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["flowers", "nature"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["boy", "male", "man", "sorry", "bow", "respect", "curtsy", "bend"],
+    "moji": "🙇"
+  },
+  "bowling": {
+    "unicode": "1F3B3",
+    "unicode_alternates": [],
+    "name": "bowling",
+    "shortname": ":bowling:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fun", "play", "sports", "bowl", "bowling", "ball", "pin", "strike", "spare", "game"],
+    "moji": "🎳"
+  },
+  "boy": {
+    "unicode": "1F466",
+    "unicode_alternates": [],
+    "name": "boy",
+    "shortname": ":boy:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["guy", "male", "man"],
+    "moji": "👦"
+  },
+  "boys_symbol": {
+    "unicode": "1F6C9",
+    "unicode_alternates": [],
+    "name": "boys symbol",
+    "shortname": ":boys_symbol:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["male", "child"]
+  },
+  "bread": {
+    "unicode": "1F35E",
+    "unicode_alternates": [],
+    "name": "bread",
+    "shortname": ":bread:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["breakfast", "food", "toast", "wheat", "bread", "loaf", "yeast"],
+    "moji": "🍞"
+  },
+  "bride_with_veil": {
+    "unicode": "1F470",
+    "unicode_alternates": [],
+    "name": "bride with veil",
+    "shortname": ":bride_with_veil:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["couple", "marriage", "wedding", "bride", "wedding", "planning", "veil", "gown", "dress", "engagement", "white"],
+    "moji": "👰"
+  },
+  "bridge_at_night": {
+    "unicode": "1F309",
+    "unicode_alternates": [],
+    "name": "bridge at night",
+    "shortname": ":bridge_at_night:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["photo", "sanfrancisco", "bridge", "night", "water", "road", "evening", "suspension", "golden", "gate"],
+    "moji": "🌉"
+  },
+  "briefcase": {
+    "unicode": "1F4BC",
+    "unicode_alternates": [],
+    "name": "briefcase",
+    "shortname": ":briefcase:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["business", "documents", "work"],
+    "moji": "💼"
+  },
+  "broken_heart": {
+    "unicode": "1F494",
+    "unicode_alternates": [],
+    "name": "broken heart",
+    "shortname": ":broken_heart:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": ["</3"],
+    "keywords": ["sad", "sorry"],
+    "moji": "💔"
+  },
+  "bug": {
+    "unicode": "1F41B",
+    "unicode_alternates": [],
+    "name": "bug",
+    "shortname": ":bug:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["insect", "nature", "bug", "insect", "virus", "error"],
+    "moji": "🐛"
+  },
+  "bulb": {
+    "unicode": "1F4A1",
+    "unicode_alternates": [],
+    "name": "electric light bulb",
+    "shortname": ":bulb:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["electricity", "light", "idea", "bulb", "light"],
+    "moji": "💡"
+  },
+  "bullettrain_front": {
+    "unicode": "1F685",
+    "unicode_alternates": [],
+    "name": "high-speed train with bullet nose",
+    "shortname": ":bullettrain_front:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "train", "bullet", "rail"],
+    "moji": "🚅"
+  },
+  "bullettrain_side": {
+    "unicode": "1F684",
+    "unicode_alternates": [],
+    "name": "high-speed train",
+    "shortname": ":bullettrain_side:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle", "train", "bullet", "rail"],
+    "moji": "🚄"
+  },
+  "bullhorn": {
+    "unicode": "1F56B",
+    "unicode_alternates": [],
+    "name": "bullhorn",
+    "shortname": ":bullhorn:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sound", "noise", "announcement", "megaphone"]
+  },
+  "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"]
+  },
+  "bus": {
+    "unicode": "1F68C",
+    "unicode_alternates": [],
+    "name": "bus",
+    "shortname": ":bus:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["car", "transportation", "vehicle", "bus", "school", "city", "transportation", "public"],
+    "moji": "🚌"
+  },
+  "busstop": {
+    "unicode": "1F68F",
+    "unicode_alternates": [],
+    "name": "bus stop",
+    "shortname": ":busstop:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "bus", "stop", "city", "transport", "transportation"],
+    "moji": "🚏"
+  },
+  "bust_in_silhouette": {
+    "unicode": "1F464",
+    "unicode_alternates": [],
+    "name": "bust in silhouette",
+    "shortname": ":bust_in_silhouette:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["human", "man", "person", "user", "silhouette", "person", "user", "member", "account", "guest", "icon", "avatar", "profile", "me", "myself", "i"],
+    "moji": "👤"
+  },
+  "busts_in_silhouette": {
+    "unicode": "1F465",
+    "unicode_alternates": [],
+    "name": "busts in silhouette",
+    "shortname": ":busts_in_silhouette:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["group", "human", "man", "person", "team", "user", "silhouette", "silhouettes", "people", "user", "members", "accounts", "relationship", "shadow"],
+    "moji": "👥"
+  },
+  "cactus": {
+    "unicode": "1F335",
+    "unicode_alternates": [],
+    "name": "cactus",
+    "shortname": ":cactus:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "plant", "vegetable", "cactus", "desert", "drought", "spike", "poke"],
+    "moji": "🌵"
+  },
+  "cake": {
+    "unicode": "1F370",
+    "unicode_alternates": [],
+    "name": "shortcake",
+    "shortname": ":cake:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["desert", "food", "cake", "short", "dessert", "strawberry"],
+    "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": [],
+    "name": "tear-off calendar",
+    "shortname": ":calendar:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["schedule"],
+    "moji": "📆"
+  },
+  "calendar_spiral": {
+    "unicode": "1F5D3",
+    "unicode_alternates": [],
+    "name": "spiral calendar pad",
+    "shortname": ":calendar_spiral:",
+    "category": "objects_symbols",
+    "aliases": [":spiral_calendar_pad:"],
+    "aliases_ascii": [],
+    "keywords": ["schedule", "date", "day"]
+  },
+  "calling": {
+    "unicode": "1F4F2",
+    "unicode_alternates": [],
+    "name": "mobile phone with rightwards arrow at left",
+    "shortname": ":calling:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["incoming", "iphone"],
+    "moji": "📲"
+  },
+  "camel": {
+    "unicode": "1F42B",
+    "unicode_alternates": [],
+    "name": "bactrian camel",
+    "shortname": ":camel:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "hot", "nature", "bactrian", "camel", "hump", "desert", "central asia", "heat", "hot", "water", "hump day", "wednesday", "sex"],
+    "moji": "🐫"
+  },
+  "camera": {
+    "unicode": "1F4F7",
+    "unicode_alternates": [],
+    "name": "camera",
+    "shortname": ":camera:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["gadgets", "photo"],
+    "moji": "📷"
+  },
+  "camera_with_flash": {
+    "unicode": "1F4F8",
+    "unicode_alternates": [],
+    "name": "camera with flash",
+    "shortname": ":camera_with_flash:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["photo", "picture"]
+  },
+  "camping": {
+    "unicode": "1F3D5",
+    "unicode_alternates": [],
+    "name": "camping",
+    "shortname": ":camping:",
+    "category": "travel_places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["outdoors", "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"]
+  },
+  "cancer": {
+    "unicode": "264B",
+    "unicode_alternates": ["264B-FE0F"],
+    "name": "cancer",
+    "shortname": ":cancer:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cancer", "crab", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+    "moji": "♋"
+  },
+  "candle": {
+    "unicode": "1F56F",
+    "unicode_alternates": [],
+    "name": "candle",
+    "shortname": ":candle:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["light", "wax"]
+  },
+  "candy": {
+    "unicode": "1F36C",
+    "unicode_alternates": [],
+    "name": "candy",
+    "shortname": ":candy:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["desert", "snack", "candy", "sugar", "sweet", "hard"],
+    "moji": "🍬"
+  },
+  "capital_abcd": {
+    "unicode": "1F520",
+    "unicode_alternates": [],
+    "name": "input symbol for latin capital letters",
+    "shortname": ":capital_abcd:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alphabet", "blue-square", "words"],
+    "moji": "🔠"
+  },
+  "capricorn": {
+    "unicode": "2651",
+    "unicode_alternates": ["2651-FE0F"],
+    "name": "capricorn",
+    "shortname": ":capricorn:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["capricorn", "sea-goat", "goat-horned", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+    "moji": "♑"
+  },
+  "card_box": {
+    "unicode": "1F5C3",
+    "unicode_alternates": [],
+    "name": "card file box",
+    "shortname": ":card_box:",
+    "category": "objects_symbols",
+    "aliases": [":card_file_box:"],
+    "aliases_ascii": [],
+    "keywords": ["index", "organization"]
+  },
+  "card_index": {
+    "unicode": "1F4C7",
+    "unicode_alternates": [],
+    "name": "card index",
+    "shortname": ":card_index:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["business", "stationery"],
+    "moji": "📇"
+  },
+  "carousel_horse": {
+    "unicode": "1F3A0",
+    "unicode_alternates": [],
+    "name": "carousel horse",
+    "shortname": ":carousel_horse:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["carnival", "horse", "photo", "carousel", "horse", "amusement", "park", "ride", "entertainment", "park", "fair"],
+    "moji": "🎠"
+  },
+  "cartridge": {
+    "unicode": "1F5AD",
+    "unicode_alternates": [],
+    "name": "tape cartridge",
+    "shortname": ":cartridge:",
+    "category": "objects_symbols",
+    "aliases": [":tape_cartridge:"],
+    "aliases_ascii": [],
+    "keywords": ["oldschool", "save", "technology", "disk", "storage", "information", "computer", "drive", "megabyte"]
+  },
+  "cat": {
+    "unicode": "1F431",
+    "unicode_alternates": [],
+    "name": "cat face",
+    "shortname": ":cat:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "meow"],
+    "moji": "🐱"
+  },
+  "cat2": {
+    "unicode": "1F408",
+    "unicode_alternates": [],
+    "name": "cat",
+    "shortname": ":cat2:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "meow", "pet", "cat", "kitten", "meow"],
+    "moji": "🐈"
+  },
+  "celtic_cross": {
+    "unicode": "1F548",
+    "unicode_alternates": [],
+    "name": "celtic cross",
+    "shortname": ":celtic_cross:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["religion", "symbol"]
+  },
+  "chart": {
+    "unicode": "1F4B9",
+    "unicode_alternates": [],
+    "name": "chart with upwards trend and yen sign",
+    "shortname": ":chart:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["graph", "green-square"],
+    "moji": "💹"
+  },
+  "chart_with_downwards_trend": {
+    "unicode": "1F4C9",
+    "unicode_alternates": [],
+    "name": "chart with downwards trend",
+    "shortname": ":chart_with_downwards_trend:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["graph"],
+    "moji": "📉"
+  },
+  "chart_with_upwards_trend": {
+    "unicode": "1F4C8",
+    "unicode_alternates": [],
+    "name": "chart with upwards trend",
+    "shortname": ":chart_with_upwards_trend:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["graph"],
+    "moji": "📈"
+  },
+  "checkered_flag": {
+    "unicode": "1F3C1",
+    "unicode_alternates": [],
+    "name": "chequered flag",
+    "shortname": ":checkered_flag:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["contest", "finishline", "gokart", "rase", "checkered", "chequred", "race", "flag", "finish", "complete", "end"],
+    "moji": "🏁"
+  },
+  "cherries": {
+    "unicode": "1F352",
+    "unicode_alternates": [],
+    "name": "cherries",
+    "shortname": ":cherries:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "fruit", "cherry", "cherries", "tree", "fruit", "pit"],
+    "moji": "🍒"
+  },
+  "cherry_blossom": {
+    "unicode": "1F338",
+    "unicode_alternates": [],
+    "name": "cherry blossom",
+    "shortname": ":cherry_blossom:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["flower", "nature", "plant", "cherry", "blossom", "tree", "flower"],
+    "moji": "🌸"
+  },
+  "chestnut": {
+    "unicode": "1F330",
+    "unicode_alternates": [],
+    "name": "chestnut",
+    "shortname": ":chestnut:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "squirrel", "chestnut", "roasted", "food", "tree"],
+    "moji": "🌰"
+  },
+  "chicken": {
+    "unicode": "1F414",
+    "unicode_alternates": [],
+    "name": "chicken",
+    "shortname": ":chicken:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "cluck", "chicken", "hen", "poultry", "livestock"],
+    "moji": "🐔"
+  },
+  "children_crossing": {
+    "unicode": "1F6B8",
+    "unicode_alternates": [],
+    "name": "children crossing",
+    "shortname": ":children_crossing:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["school", "children", "kids", "caution", "crossing", "street", "crosswalk", "slow"],
+    "moji": "🚸"
+  },
+  "chipmunk": {
+    "unicode": "1F43F",
+    "unicode_alternates": [],
+    "name": "chipmunk",
+    "shortname": ":chipmunk:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature"]
+  },
+  "chocolate_bar": {
+    "unicode": "1F36B",
+    "unicode_alternates": [],
+    "name": "chocolate bar",
+    "shortname": ":chocolate_bar:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["desert", "food", "snack", "chocolate", "bar", "candy", "coca", "hershey&#039;s"],
+    "moji": "🍫"
+  },
+  "christmas_tree": {
+    "unicode": "1F384",
+    "unicode_alternates": [],
+    "name": "christmas tree",
+    "shortname": ":christmas_tree:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["celebration", "december", "festival", "vacation", "xmas", "christmas", "xmas", "santa", "holiday", "winter", "december", "santa", "evergreen", "ornaments", "jesus", "gifts", "presents"],
+    "moji": "🎄"
+  },
+  "church": {
+    "unicode": "26EA",
+    "unicode_alternates": ["26EA-FE0F"],
+    "name": "church",
+    "shortname": ":church:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building", "christ", "religion"],
+    "moji": "⛪"
+  },
+  "cinema": {
+    "unicode": "1F3A6",
+    "unicode_alternates": [],
+    "name": "cinema",
+    "shortname": ":cinema:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "film", "movie", "record", "cinema", "movie", "theater", "motion", "picture"],
+    "moji": "🎦"
+  },
+  "circus_tent": {
+    "unicode": "1F3AA",
+    "unicode_alternates": [],
+    "name": "circus tent",
+    "shortname": ":circus_tent:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["carnival", "festival", "party", "circus", "tent", "event", "carnival", "big", "top", "canvas"],
+    "moji": "🎪"
+  },
+  "city_dusk": {
+    "unicode": "1F306",
+    "unicode_alternates": [],
+    "name": "cityscape at dusk",
+    "shortname": ":city_dusk:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["photo", "city", "scape", "sunset", "dusk", "lights", "evening", "metropolitan", "night", "dark"],
+    "moji": "🌆"
+  },
+  "city_sunset": {
+    "unicode": "1F307",
+    "unicode_alternates": [],
+    "name": "sunset over buildings",
+    "shortname": ":city_sunset:",
+    "category": "places",
+    "aliases": [":city_sunrise:"],
+    "aliases_ascii": [],
+    "keywords": ["photo", "city", "scape", "sunrise", "dawn", "light", "morning", "metropolitan", "rise", "sun"],
+    "moji": "🌇"
+  },
+  "cityscape": {
+    "unicode": "1F3D9",
+    "unicode_alternates": [],
+    "name": "cityscape",
+    "shortname": ":cityscape:",
+    "category": "travel_places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["skyscraper", "city", "view", "lights", "buiildings", "metropolis"]
+  },
+  "clap": {
+    "unicode": "1F44F",
+    "unicode_alternates": [],
+    "name": "clapping hands sign",
+    "shortname": ":clap:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["applause", "congrats", "hands", "praise", "clapping", "appreciation", "approval", "sound", "encouragement", "enthusiasm"],
+    "moji": "👏"
+  },
+  "clapper": {
+    "unicode": "1F3AC",
+    "unicode_alternates": [],
+    "name": "clapper board",
+    "shortname": ":clapper:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["film", "movie", "record", "clapper", "board", "clapboard", "movie", "film", "take"],
+    "moji": "🎬"
+  },
+  "classical_building": {
+    "unicode": "1F3DB",
+    "unicode_alternates": [],
+    "name": "classical building",
+    "shortname": ":classical_building:",
+    "category": "travel_places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["government", "architecture", "history", "iconic", "genre"]
+  },
+  "clipboard": {
+    "unicode": "1F4CB",
+    "unicode_alternates": [],
+    "name": "clipboard",
+    "shortname": ":clipboard:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["documents", "stationery"],
+    "moji": "📋"
+  },
+  "clock": {
+    "unicode": "1F570",
+    "unicode_alternates": [],
+    "name": "mantlepiece clock",
+    "shortname": ":clock:",
+    "category": "objects_symbols",
+    "aliases": [":mantlepiece_clock:"],
+    "aliases_ascii": [],
+    "keywords": ["time"]
+  },
+  "clock1": {
+    "unicode": "1F550",
+    "unicode_alternates": [],
+    "name": "clock face one oclock",
+    "shortname": ":clock1:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕐"
+  },
+  "clock10": {
+    "unicode": "1F559",
+    "unicode_alternates": [],
+    "name": "clock face ten oclock",
+    "shortname": ":clock10:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕙"
+  },
+  "clock1030": {
+    "unicode": "1F565",
+    "unicode_alternates": [],
+    "name": "clock face ten-thirty",
+    "shortname": ":clock1030:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕥"
+  },
+  "clock11": {
+    "unicode": "1F55A",
+    "unicode_alternates": [],
+    "name": "clock face eleven oclock",
+    "shortname": ":clock11:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕚"
+  },
+  "clock1130": {
+    "unicode": "1F566",
+    "unicode_alternates": [],
+    "name": "clock face eleven-thirty",
+    "shortname": ":clock1130:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕦"
+  },
+  "clock12": {
+    "unicode": "1F55B",
+    "unicode_alternates": [],
+    "name": "clock face twelve oclock",
+    "shortname": ":clock12:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕛"
+  },
+  "clock1230": {
+    "unicode": "1F567",
+    "unicode_alternates": [],
+    "name": "clock face twelve-thirty",
+    "shortname": ":clock1230:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"]
+  },
+  "clock130": {
+    "unicode": "1F55C",
+    "unicode_alternates": [],
+    "name": "clock face one-thirty",
+    "shortname": ":clock130:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕜"
+  },
+  "clock2": {
+    "unicode": "1F551",
+    "unicode_alternates": [],
+    "name": "clock face two oclock",
+    "shortname": ":clock2:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕑"
+  },
+  "clock230": {
+    "unicode": "1F55D",
+    "unicode_alternates": [],
+    "name": "clock face two-thirty",
+    "shortname": ":clock230:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕝"
+  },
+  "clock3": {
+    "unicode": "1F552",
+    "unicode_alternates": [],
+    "name": "clock face three oclock",
+    "shortname": ":clock3:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕒"
+  },
+  "clock330": {
+    "unicode": "1F55E",
+    "unicode_alternates": [],
+    "name": "clock face three-thirty",
+    "shortname": ":clock330:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕞"
+  },
+  "clock4": {
+    "unicode": "1F553",
+    "unicode_alternates": [],
+    "name": "clock face four oclock",
+    "shortname": ":clock4:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕓"
+  },
+  "clock430": {
+    "unicode": "1F55F",
+    "unicode_alternates": [],
+    "name": "clock face four-thirty",
+    "shortname": ":clock430:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕟"
+  },
+  "clock5": {
+    "unicode": "1F554",
+    "unicode_alternates": [],
+    "name": "clock face five oclock",
+    "shortname": ":clock5:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕔"
+  },
+  "clock530": {
+    "unicode": "1F560",
+    "unicode_alternates": [],
+    "name": "clock face five-thirty",
+    "shortname": ":clock530:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕠"
+  },
+  "clock6": {
+    "unicode": "1F555",
+    "unicode_alternates": [],
+    "name": "clock face six oclock",
+    "shortname": ":clock6:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕕"
+  },
+  "clock630": {
+    "unicode": "1F561",
+    "unicode_alternates": [],
+    "name": "clock face six-thirty",
+    "shortname": ":clock630:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕡"
+  },
+  "clock7": {
+    "unicode": "1F556",
+    "unicode_alternates": [],
+    "name": "clock face seven oclock",
+    "shortname": ":clock7:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕖"
+  },
+  "clock730": {
+    "unicode": "1F562",
+    "unicode_alternates": [],
+    "name": "clock face seven-thirty",
+    "shortname": ":clock730:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕢"
+  },
+  "clock8": {
+    "unicode": "1F557",
+    "unicode_alternates": [],
+    "name": "clock face eight oclock",
+    "shortname": ":clock8:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕗"
+  },
+  "clock830": {
+    "unicode": "1F563",
+    "unicode_alternates": [],
+    "name": "clock face eight-thirty",
+    "shortname": ":clock830:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕣"
+  },
+  "clock9": {
+    "unicode": "1F558",
+    "unicode_alternates": [],
+    "name": "clock face nine oclock",
+    "shortname": ":clock9:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "moji": "🕘"
+  },
+  "clock930": {
+    "unicode": "1F564",
+    "unicode_alternates": [],
+    "name": "clock face nine-thirty",
+    "shortname": ":clock930:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "time"],
+    "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": [],
+    "name": "closed book",
+    "shortname": ":closed_book:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["knowledge", "library", "read"],
+    "moji": "📕"
+  },
+  "closed_lock_with_key": {
+    "unicode": "1F510",
+    "unicode_alternates": [],
+    "name": "closed lock with key",
+    "shortname": ":closed_lock_with_key:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["privacy", "security"],
+    "moji": "🔐"
+  },
+  "closed_umbrella": {
+    "unicode": "1F302",
+    "unicode_alternates": [],
+    "name": "closed umbrella",
+    "shortname": ":closed_umbrella:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["drizzle", "rain", "weather", "umbrella", "closed", "rain", "moisture", "protection", "sun", "ultraviolet", "uv"],
+    "moji": "🌂"
+  },
+  "cloud": {
+    "unicode": "2601",
+    "unicode_alternates": ["2601-FE0F"],
+    "name": "cloud",
+    "shortname": ":cloud:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sky", "weather"],
+    "moji": "☁"
+  },
+  "cloud_lightning": {
+    "unicode": "1F329",
+    "unicode_alternates": [],
+    "name": "cloud with lightning",
+    "shortname": ":cloud_lightning:",
+    "category": "nature",
+    "aliases": [":cloud_with_lightning:"],
+    "aliases_ascii": [],
+    "keywords": ["weather", "thunder"]
+  },
+  "cloud_rain": {
+    "unicode": "1F327",
+    "unicode_alternates": [],
+    "name": "cloud with rain",
+    "shortname": ":cloud_rain:",
+    "category": "nature",
+    "aliases": [":cloud_with_rain:"],
+    "aliases_ascii": [],
+    "keywords": ["weather", "wet"]
+  },
+  "cloud_snow": {
+    "unicode": "1F328",
+    "unicode_alternates": [],
+    "name": "cloud with snow",
+    "shortname": ":cloud_snow:",
+    "category": "nature",
+    "aliases": [":cloud_with_snow:"],
+    "aliases_ascii": [],
+    "keywords": ["weather", "cold"]
+  },
+  "cloud_tornado": {
+    "unicode": "1F32A",
+    "unicode_alternates": [],
+    "name": "cloud with tornado",
+    "shortname": ":cloud_tornado:",
+    "category": "nature",
+    "aliases": [":cloud_with_tornado:"],
+    "aliases_ascii": [],
+    "keywords": ["weather", "destruction", "funnel"]
+  },
+  "clubs": {
+    "unicode": "2663",
+    "unicode_alternates": ["2663-FE0F"],
+    "name": "black club suit",
+    "shortname": ":clubs:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cards", "poker"],
+    "moji": "♣"
+  },
+  "cocktail": {
+    "unicode": "1F378",
+    "unicode_alternates": [],
+    "name": "cocktail glass",
+    "shortname": ":cocktail:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alcohol", "beverage", "drink", "drunk", "cocktail", "mixed", "drink", "alcohol", "glass", "martini", "bar"],
+    "moji": "🍸"
+  },
+  "coffee": {
+    "unicode": "2615",
+    "unicode_alternates": ["2615-FE0F"],
+    "name": "hot beverage",
+    "shortname": ":coffee:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["beverage", "cafe", "drink", "espresso"],
+    "moji": "☕"
+  },
+  "cold_sweat": {
+    "unicode": "1F630",
+    "unicode_alternates": [],
+    "name": "face with open mouth and cold sweat",
+    "shortname": ":cold_sweat:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "nervous", "sweat", "exasperated", "frustrated"],
+    "moji": "😰"
+  },
+  "compression": {
+    "unicode": "1F5DC",
+    "unicode_alternates": [],
+    "name": "compression",
+    "shortname": ":compression:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["reduce"]
+  },
+  "computer": {
+    "unicode": "1F4BB",
+    "unicode_alternates": [],
+    "name": "personal computer",
+    "shortname": ":computer:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["laptop", "tech"],
+    "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": [],
+    "name": "confetti ball",
+    "shortname": ":confetti_ball:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["festival", "party", "party", "congratulations", "confetti", "ball", "celebrate", "win", "birthday", "new years", "wedding"],
+    "moji": "🎊"
+  },
+  "confounded": {
+    "unicode": "1F616",
+    "unicode_alternates": [],
+    "name": "confounded face",
+    "shortname": ":confounded:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["confused", "face", "sick", "unwell", "confound", "amaze", "perplex", "puzzle", "mystify"],
+    "moji": "😖"
+  },
+  "confused": {
+    "unicode": "1F615",
+    "unicode_alternates": [],
+    "name": "confused face",
+    "shortname": ":confused:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [">:\\", ">:/", ":-/", ":-.", ":/", ":\\", "=/", "=\\", ":L", "=L"],
+    "keywords": ["confused", "confuse", "daze", "perplex", "puzzle", "indifference", "skeptical", "undecided", "uneasy", "hesitant"],
+    "moji": "😕"
+  },
+  "congratulations": {
+    "unicode": "3297",
+    "unicode_alternates": ["3297-FE0F"],
+    "name": "circled ideograph congratulation",
+    "shortname": ":congratulations:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "japanese", "kanji"],
+    "moji": "㊗"
+  },
+  "construction": {
+    "unicode": "1F6A7",
+    "unicode_alternates": [],
+    "name": "construction sign",
+    "shortname": ":construction:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["caution", "progress", "wip"],
+    "moji": "🚧"
+  },
+  "construction_worker": {
+    "unicode": "1F477",
+    "unicode_alternates": [],
+    "name": "construction worker",
+    "shortname": ":construction_worker:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["human", "male", "man", "wip"],
+    "moji": "👷"
+  },
+  "control_knobs": {
+    "unicode": "1F39B",
+    "unicode_alternates": [],
+    "name": "control knobs",
+    "shortname": ":control_knobs:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["dial"]
+  },
+  "contruction_site": {
+    "unicode": "1F3D7",
+    "unicode_alternates": [],
+    "name": "building construction",
+    "shortname": ":contruction_site:",
+    "category": "travel_places",
+    "aliases": [":building_construction:"],
+    "aliases_ascii": [],
+    "keywords": ["site", "work"]
+  },
+  "convenience_store": {
+    "unicode": "1F3EA",
+    "unicode_alternates": [],
+    "name": "convenience store",
+    "shortname": ":convenience_store:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building"],
+    "moji": "🏪"
+  },
+  "cookie": {
+    "unicode": "1F36A",
+    "unicode_alternates": [],
+    "name": "cookie",
+    "shortname": ":cookie:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chocolate", "food", "oreo", "snack", "cookie", "dessert", "biscuit", "sweet", "chocolate"],
+    "moji": "🍪"
+  },
+  "cool": {
+    "unicode": "1F192",
+    "unicode_alternates": [],
+    "name": "squared cool",
+    "shortname": ":cool:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "words"],
+    "moji": "🆒"
+  },
+  "cop": {
+    "unicode": "1F46E",
+    "unicode_alternates": [],
+    "name": "police officer",
+    "shortname": ":cop:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrest", "enforcement", "law", "man", "police"],
+    "moji": "👮"
+  },
+  "copyright": {
+    "moji": "©",
+    "unicode": "00A9",
+    "unicode_alternates": [],
+    "name": "copyright sign",
+    "shortname": ":copyright:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["ip", "license"]
+  },
+  "corn": {
+    "unicode": "1F33D",
+    "unicode_alternates": [],
+    "name": "ear of maize",
+    "shortname": ":corn:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "plant", "vegetable", "corn", "maize", "food", "iowa", "kernel", "popcorn", "husk", "yellow", "stalk", "cob", "ear"],
+    "moji": "🌽"
+  },
+  "couch": {
+    "unicode": "1F6CB",
+    "unicode_alternates": [],
+    "name": "couch and lamp",
+    "shortname": ":couch:",
+    "category": "travel_places",
+    "aliases": [":couch_and_lamp:"],
+    "aliases_ascii": [],
+    "keywords": ["lounge", "sectional", "sofa", "loveseat", "leather", "microfiber", "sit", "relax"]
+  },
+  "couple": {
+    "unicode": "1F46B",
+    "unicode_alternates": [],
+    "name": "man and woman holding hands",
+    "shortname": ":couple:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "date", "dating", "human", "like", "love", "marriage", "people", "valentines"],
+    "moji": "👫"
+  },
+  "couple_mm": {
+    "unicode": "1F468-2764-1F468",
+    "unicode_alternates": ["1F468-200D-2764-FE0F-200D-1F468"],
+    "name": "couple (man,man)",
+    "shortname": ":couple_mm:",
+    "category": "people",
+    "aliases": [":couple_with_heart_mm:"],
+    "aliases_ascii": [],
+    "keywords": ["affection", "dating", "human", "like", "love", "marriage", "valentines"]
+  },
+  "couple_with_heart": {
+    "unicode": "1F491",
+    "unicode_alternates": [],
+    "name": "couple with heart",
+    "shortname": ":couple_with_heart:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "dating", "human", "like", "love", "marriage", "valentines"],
+    "moji": "💑"
+  },
+  "couple_ww": {
+    "unicode": "1F469-2764-1F469",
+    "unicode_alternates": ["1F469-200D-2764-FE0F-200D-1F469"],
+    "name": "couple (woman,woman)",
+    "shortname": ":couple_ww:",
+    "category": "people",
+    "aliases": [":couple_with_heart_ww:"],
+    "aliases_ascii": [],
+    "keywords": ["affection", "dating", "human", "like", "love", "marriage", "valentines"]
+  },
+  "couplekiss": {
+    "unicode": "1F48F",
+    "unicode_alternates": [],
+    "name": "kiss",
+    "shortname": ":couplekiss:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["dating", "like", "love", "marriage", "valentines"],
+    "moji": "💏"
+  },
+  "cow": {
+    "unicode": "1F42E",
+    "unicode_alternates": [],
+    "name": "cow face",
+    "shortname": ":cow:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "beef", "ox"],
+    "moji": "🐮"
+  },
+  "cow2": {
+    "unicode": "1F404",
+    "unicode_alternates": [],
+    "name": "cow",
+    "shortname": ":cow2:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "beef", "nature", "ox", "cow", "milk", "dairy", "beef", "bessie", "moo"],
+    "moji": "🐄"
+  },
+  "crayon": {
+    "unicode": "1F58D",
+    "unicode_alternates": [],
+    "name": "lower left crayon",
+    "shortname": ":crayon:",
+    "category": "objects_symbols",
+    "aliases": [":lower_left_crayon:"],
+    "aliases_ascii": [],
+    "keywords": ["write", "draw", "color", "wax"]
+  },
+  "credit_card": {
+    "unicode": "1F4B3",
+    "unicode_alternates": [],
+    "name": "credit card",
+    "shortname": ":credit_card:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bill", "dollar", "money", "pay", "payment", "credit", "card", "loan", "purchase", "shopping", "mastercard", "visa", "american express", "wallet", "signature"],
+    "moji": "💳"
+  },
+  "crescent_moon": {
+    "unicode": "1F319",
+    "unicode_alternates": [],
+    "name": "crescent moon",
+    "shortname": ":crescent_moon:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["night", "moon", "crescent", "waxing", "sky", "night", "cheese", "phase"],
+    "moji": "🌙"
+  },
+  "crocodile": {
+    "unicode": "1F40A",
+    "unicode_alternates": [],
+    "name": "crocodile",
+    "shortname": ":crocodile:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "crocodile", "croc", "alligator", "gator", "cranky"],
+    "moji": "🐊"
+  },
+  "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"]
+  },
+  "crossed_flags": {
+    "unicode": "1F38C",
+    "unicode_alternates": [],
+    "name": "crossed flags",
+    "shortname": ":crossed_flags:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["japan"],
+    "moji": "🎌"
+  },
+  "crown": {
+    "unicode": "1F451",
+    "unicode_alternates": [],
+    "name": "crown",
+    "shortname": ":crown:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["king", "kod", "leader", "royalty"],
+    "moji": "👑"
+  },
+  "cruise_ship": {
+    "unicode": "1F6F3",
+    "unicode_alternates": [],
+    "name": "passenger ship",
+    "shortname": ":cruise_ship:",
+    "category": "travel_places",
+    "aliases": [":passenger_ship:"],
+    "aliases_ascii": [],
+    "keywords": ["titanic", "transportation", "boat"]
+  },
+  "cry": {
+    "unicode": "1F622",
+    "unicode_alternates": [],
+    "name": "crying face",
+    "shortname": ":cry:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [":'(", ":'-(", ";(", ";-("],
+    "keywords": ["face", "sad", "sad", "cry", "tear", "weep", "tears"],
+    "moji": "😢"
+  },
+  "crying_cat_face": {
+    "unicode": "1F63F",
+    "unicode_alternates": [],
+    "name": "crying cat face",
+    "shortname": ":crying_cat_face:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "cats", "sad", "tears", "weep", "cry", "cat", "sob", "tears", "sad", "melancholy", "morn", "somber", "hurt"],
+    "moji": "😿"
+  },
+  "crystal_ball": {
+    "unicode": "1F52E",
+    "unicode_alternates": [],
+    "name": "crystal ball",
+    "shortname": ":crystal_ball:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["disco", "party"],
+    "moji": "🔮"
+  },
+  "cupid": {
+    "unicode": "1F498",
+    "unicode_alternates": [],
+    "name": "heart with arrow",
+    "shortname": ":cupid:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "heart", "like", "love", "valentines"],
+    "moji": "💘"
+  },
+  "curly_loop": {
+    "unicode": "27B0",
+    "unicode_alternates": [],
+    "name": "curly loop",
+    "shortname": ":curly_loop:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["scribble"],
+    "moji": "➰"
+  },
+  "currency_exchange": {
+    "unicode": "1F4B1",
+    "unicode_alternates": [],
+    "name": "currency exchange",
+    "shortname": ":currency_exchange:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["dollar", "money", "travel"],
+    "moji": "💱"
+  },
+  "curry": {
+    "unicode": "1F35B",
+    "unicode_alternates": [],
+    "name": "curry and rice",
+    "shortname": ":curry:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "hot", "indian", "spicy", "curry", "spice", "flavor", "food", "meal"],
+    "moji": "🍛"
+  },
+  "custard": {
+    "unicode": "1F36E",
+    "unicode_alternates": [],
+    "name": "custard",
+    "shortname": ":custard:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["desert", "food", "custard", "cream", "rich", "butter", "dessert", "crème", "brûlée", "french"],
+    "moji": "🍮"
+  },
+  "customs": {
+    "unicode": "1F6C3",
+    "unicode_alternates": [],
+    "name": "customs",
+    "shortname": ":customs:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["border", "passport", "customs", "travel", "foreign", "goods", "check", "authority", "government"],
+    "moji": "🛃"
+  },
+  "cyclone": {
+    "moji": "🌀",
+    "unicode": "1F300",
+    "unicode_alternates": [],
+    "name": "cyclone",
+    "shortname": ":cyclone:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue", "cloud", "swirl", "weather", "cyclone", "hurricane", "typhoon", "storm", "ocean"]
+  },
+  "dagger": {
+    "unicode": "1F5E1",
+    "unicode_alternates": [],
+    "name": "dagger knife",
+    "shortname": ":dagger:",
+    "category": "objects_symbols",
+    "aliases": [":dagger_knife:"],
+    "aliases_ascii": [],
+    "keywords": ["blade", "knife"]
+  },
+  "dancer": {
+    "unicode": "1F483",
+    "unicode_alternates": [],
+    "name": "dancer",
+    "shortname": ":dancer:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["female", "fun", "girl", "woman", "dance", "dancer", "dress", "fancy", "boogy", "party", "celebrate", "ballet", "tango", "cha cha", "music"],
+    "moji": "💃"
+  },
+  "dancers": {
+    "unicode": "1F46F",
+    "unicode_alternates": [],
+    "name": "woman with bunny ears",
+    "shortname": ":dancers:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bunny", "female", "girls", "women", "dancing", "dancers", "showgirl", "playboy", "costume", "bunny", "cancan"],
+    "moji": "👯"
+  },
+  "dango": {
+    "unicode": "1F361",
+    "unicode_alternates": [],
+    "name": "dango",
+    "shortname": ":dango:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "dango", "japanese", "dumpling", "mochi", "balls", "skewer"],
+    "moji": "🍡"
+  },
+  "dark_sunglasses": {
+    "unicode": "1F576",
+    "unicode_alternates": [],
+    "name": "dark sunglasses",
+    "shortname": ":dark_sunglasses:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shades", "eyes"]
+  },
+  "dart": {
+    "unicode": "1F3AF",
+    "unicode_alternates": [],
+    "name": "direct hit",
+    "shortname": ":dart:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bar", "game", "direct", "hit", "bullseye", "dart", "archery", "game", "fletching", "arrow", "sport"],
+    "moji": "🎯"
+  },
+  "dash": {
+    "unicode": "1F4A8",
+    "unicode_alternates": [],
+    "name": "dash symbol",
+    "shortname": ":dash:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["air", "fast", "shoo", "wind"],
+    "moji": "💨"
+  },
+  "date": {
+    "unicode": "1F4C5",
+    "unicode_alternates": [],
+    "name": "calendar",
+    "shortname": ":date:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["calendar", "schedule"],
+    "moji": "📅"
+  },
+  "deciduous_tree": {
+    "unicode": "1F333",
+    "unicode_alternates": [],
+    "name": "deciduous tree",
+    "shortname": ":deciduous_tree:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "plant", "deciduous", "tree", "leaves", "fall", "color"],
+    "moji": "🌳"
+  },
+  "department_store": {
+    "unicode": "1F3EC",
+    "unicode_alternates": [],
+    "name": "department store",
+    "shortname": ":department_store:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building", "mall", "shopping", "department", "store", "retail", "sale", "merchandise"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["hot", "dry", "sandy", "cactus", "sunny", "barren"]
+  },
+  "desktop": {
+    "unicode": "1F5A5",
+    "unicode_alternates": [],
+    "name": "desktop computer",
+    "shortname": ":desktop:",
+    "category": "objects_symbols",
+    "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"]
+  },
+  "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["diamond", "cute", "cuteness", "kawaii", "japanese", "glyph", "adorable"],
+    "moji": "💠"
+  },
+  "diamonds": {
+    "unicode": "2666",
+    "unicode_alternates": ["2666-FE0F"],
+    "name": "black diamond suit",
+    "shortname": ":diamonds:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cards", "poker"],
+    "moji": "♦"
+  },
+  "disappointed": {
+    "unicode": "1F61E",
+    "unicode_alternates": [],
+    "name": "disappointed face",
+    "shortname": ":disappointed:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [">:[", ":-(", ":(", ":-[", ":[", "=("],
+    "keywords": ["disappointed", "disappoint", "frown", "depressed", "discouraged", "face", "sad", "upset"],
+    "moji": "😞"
+  },
+  "disappointed_relieved": {
+    "unicode": "1F625",
+    "unicode_alternates": [],
+    "name": "disappointed but relieved face",
+    "shortname": ":disappointed_relieved:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "nervous", "phew", "sweat", "disappoint", "relief"],
+    "moji": "😥"
+  },
+  "dividers": {
+    "unicode": "1F5C2",
+    "unicode_alternates": [],
+    "name": "card index dividers",
+    "shortname": ":dividers:",
+    "category": "objects_symbols",
+    "aliases": [":card_index_dividers:"],
+    "aliases_ascii": [],
+    "keywords": ["stationery", "rolodex"]
+  },
+  "dizzy": {
+    "unicode": "1F4AB",
+    "unicode_alternates": [],
+    "name": "dizzy symbol",
+    "shortname": ":dizzy:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shoot", "sparkle", "star", "dizzy", "drunk", "sick", "intoxicated", "squeans", "starburst", "star"],
+    "moji": "💫"
+  },
+  "dizzy_face": {
+    "unicode": "1F635",
+    "unicode_alternates": [],
+    "name": "dizzy face",
+    "shortname": ":dizzy_face:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": ["#-)", "#)", "%-)", "%)", "X)", "X-)"],
+    "keywords": ["dizzy", "drunk", "inebriated", "face", "spent", "unconscious", "xox"],
+    "moji": "😵"
+  },
+  "do_not_litter": {
+    "unicode": "1F6AF",
+    "unicode_alternates": [],
+    "name": "do not litter symbol",
+    "shortname": ":do_not_litter:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bin", "garbage", "trash", "litter", "garbage", "waste", "no", "can", "trash"],
+    "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": [],
+    "name": "dog face",
+    "shortname": ":dog:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "friend", "nature", "woof"],
+    "moji": "🐶"
+  },
+  "dog2": {
+    "unicode": "1F415",
+    "unicode_alternates": [],
+    "name": "dog",
+    "shortname": ":dog2:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "doge", "friend", "nature", "pet", "dog", "puppy", "pet", "friend", "woof", "bark", "fido"],
+    "moji": "🐕"
+  },
+  "dollar": {
+    "unicode": "1F4B5",
+    "unicode_alternates": [],
+    "name": "banknote with dollar sign",
+    "shortname": ":dollar:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bill", "currency", "money", "dollar", "united states", "canada", "australia", "banknote", "money", "currency", "paper", "cash", "bills"],
+    "moji": "💵"
+  },
+  "dolls": {
+    "unicode": "1F38E",
+    "unicode_alternates": [],
+    "name": "japanese dolls",
+    "shortname": ":dolls:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["japanese", "kimono", "toy", "dolls", "japan", "japanese", "day", "girls", "emperor", "empress", "pray", "blessing", "imperial", "family", "royal"],
+    "moji": "🎎"
+  },
+  "dolphin": {
+    "unicode": "1F42C",
+    "unicode_alternates": [],
+    "name": "dolphin",
+    "shortname": ":dolphin:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "fins", "fish", "flipper", "nature", "ocean", "sea"],
+    "moji": "🐬"
+  },
+  "door": {
+    "unicode": "1F6AA",
+    "unicode_alternates": [],
+    "name": "door",
+    "shortname": ":door:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["entry", "exit", "house", "door", "doorway", "entrance", "enter", "exit", "entry"],
+    "moji": "🚪"
+  },
+  "doughnut": {
+    "unicode": "1F369",
+    "unicode_alternates": [],
+    "name": "doughnut",
+    "shortname": ":doughnut:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["desert", "food", "snack", "sweet", "doughnut", "donut", "pastry", "fried", "dessert", "breakfast", "police", "homer", "sweet"],
+    "moji": "🍩"
+  },
+  "dove": {
+    "unicode": "1F54A",
+    "unicode_alternates": [],
+    "name": "dove of peace",
+    "shortname": ":dove:",
+    "category": "objects_symbols",
+    "aliases": [":dove_of_peace:"],
+    "aliases_ascii": [],
+    "keywords": ["symbol", "bird"]
+  },
+  "dragon": {
+    "unicode": "1F409",
+    "unicode_alternates": [],
+    "name": "dragon",
+    "shortname": ":dragon:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "chinese", "green", "myth", "nature", "dragon", "fire", "legendary", "myth"],
+    "moji": "🐉"
+  },
+  "dragon_face": {
+    "unicode": "1F432",
+    "unicode_alternates": [],
+    "name": "dragon face",
+    "shortname": ":dragon_face:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "chinese", "green", "myth", "nature", "dragon", "head", "fire", "legendary", "myth"],
+    "moji": "🐲"
+  },
+  "dress": {
+    "unicode": "1F457",
+    "unicode_alternates": [],
+    "name": "dress",
+    "shortname": ":dress:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clothes", "fashion"],
+    "moji": "👗"
+  },
+  "dromedary_camel": {
+    "unicode": "1F42A",
+    "unicode_alternates": [],
+    "name": "dromedary camel",
+    "shortname": ":dromedary_camel:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "desert", "hot", "dromedary", "camel", "hump", "desert", "middle east", "heat", "hot", "water", "hump day", "wednesday", "sex"],
+    "moji": "🐪"
+  },
+  "droplet": {
+    "unicode": "1F4A7",
+    "unicode_alternates": [],
+    "name": "droplet",
+    "shortname": ":droplet:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["drip", "faucet", "water", "drop", "droplet", "h20", "water", "aqua", "tear", "sweat", "rain", "moisture", "wet", "moist", "spit"],
+    "moji": "💧"
+  },
+  "dvd": {
+    "unicode": "1F4C0",
+    "unicode_alternates": [],
+    "name": "dvd",
+    "shortname": ":dvd:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cd", "disc", "disk"],
+    "moji": "📀"
+  },
+  "e-mail": {
+    "unicode": "1F4E7",
+    "unicode_alternates": [],
+    "name": "e-mail symbol",
+    "shortname": ":e-mail:",
+    "category": "objects",
+    "aliases": [":email:"],
+    "aliases_ascii": [],
+    "keywords": ["communication", "inbox"],
+    "moji": "📧"
+  },
+  "ear": {
+    "unicode": "1F442",
+    "unicode_alternates": [],
+    "name": "ear",
+    "shortname": ":ear:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "hear", "listen", "sound"],
+    "moji": "👂"
+  },
+  "ear_of_rice": {
+    "unicode": "1F33E",
+    "unicode_alternates": [],
+    "name": "ear of rice",
+    "shortname": ":ear_of_rice:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "plant", "ear", "rice", "food", "plant", "seed"],
+    "moji": "🌾"
+  },
+  "earth_africa": {
+    "unicode": "1F30D",
+    "unicode_alternates": [],
+    "name": "earth globe europe-africa",
+    "shortname": ":earth_africa:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["globe", "international", "world", "earth", "globe", "space", "planet", "africa", "europe", "home"],
+    "moji": "🌍"
+  },
+  "earth_americas": {
+    "unicode": "1F30E",
+    "unicode_alternates": [],
+    "name": "earth globe americas",
+    "shortname": ":earth_americas:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["USA", "globe", "international", "world", "earth", "globe", "space", "planet", "north", "south", "america", "americas", "home"],
+    "moji": "🌎"
+  },
+  "earth_asia": {
+    "unicode": "1F30F",
+    "unicode_alternates": [],
+    "name": "earth globe asia-australia",
+    "shortname": ":earth_asia:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["east", "globe", "international", "world", "earth", "globe", "space", "planet", "asia", "australia", "home"],
+    "moji": "🌏"
+  },
+  "egg": {
+    "unicode": "1F373",
+    "unicode_alternates": [],
+    "name": "cooking",
+    "shortname": ":egg:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["breakfast", "food", "egg", "fry", "pan", "flat", "cook", "frying", "cooking", "utensil"],
+    "moji": "🍳"
+  },
+  "eggplant": {
+    "unicode": "1F346",
+    "unicode_alternates": [],
+    "name": "aubergine",
+    "shortname": ":eggplant:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["aubergine", "food", "nature", "vegetable", "eggplant", "aubergine", "fruit", "purple", "penis"],
+    "moji": "🍆"
+  },
+  "eight": {
+    "moji": "8️⃣",
+    "unicode": "0038-20E3",
+    "unicode_alternates": ["0038-FE0F-20E3"],
+    "name": "digit eight",
+    "shortname": ":eight:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["8", "blue-square", "numbers"]
+  },
+  "eight_pointed_black_star": {
+    "unicode": "2734",
+    "unicode_alternates": ["2734-FE0F"],
+    "name": "eight pointed black star",
+    "shortname": ":eight_pointed_black_star:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "✴"
+  },
+  "eight_spoked_asterisk": {
+    "unicode": "2733",
+    "unicode_alternates": ["2733-FE0F"],
+    "name": "eight spoked asterisk",
+    "shortname": ":eight_spoked_asterisk:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["green-square", "sparkle", "star"],
+    "moji": "✳"
+  },
+  "electric_plug": {
+    "unicode": "1F50C",
+    "unicode_alternates": [],
+    "name": "electric plug",
+    "shortname": ":electric_plug:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["charger", "power"],
+    "moji": "🔌"
+  },
+  "elephant": {
+    "unicode": "1F418",
+    "unicode_alternates": [],
+    "name": "elephant",
+    "shortname": ":elephant:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "nose", "thailand"],
+    "moji": "🐘"
+  },
+  "end": {
+    "unicode": "1F51A",
+    "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:"],
+    "aliases_ascii": [],
+    "keywords": ["communication", "letter", "mail", "postal"]
+  },
+  "envelope_stamped_pen": {
+    "unicode": "1F586",
+    "unicode_alternates": [],
+    "name": "pen over stamped envelope",
+    "shortname": ":envelope_stamped_pen:",
+    "category": "objects_symbols",
+    "aliases": [":pen_over_stamped_envelope:"],
+    "aliases_ascii": [],
+    "keywords": ["communication", "letter", "mail", "postal"]
+  },
+  "envelope_with_arrow": {
+    "unicode": "1F4E9",
+    "unicode_alternates": [],
+    "name": "envelope with downwards arrow above",
+    "shortname": ":envelope_with_arrow:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["email"],
+    "moji": "📩"
+  },
+  "euro": {
+    "unicode": "1F4B6",
+    "unicode_alternates": [],
+    "name": "banknote with euro sign",
+    "shortname": ":euro:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["currency", "dollar", "money", "euro", "europe", "banknote", "money", "currency", "paper", "cash", "bills"],
+    "moji": "💶"
+  },
+  "european_castle": {
+    "unicode": "1F3F0",
+    "unicode_alternates": [],
+    "name": "european castle",
+    "shortname": ":european_castle:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building", "history", "royalty", "castle", "european", "residence", "royalty", "disneyland", "disney", "fort", "fortified", "moat", "tower", "princess", "prince", "lord", "king", "queen", "fortress", "nobel", "stronghold"],
+    "moji": "🏰"
+  },
+  "european_post_office": {
+    "unicode": "1F3E4",
+    "unicode_alternates": [],
+    "name": "european post office",
+    "shortname": ":european_post_office:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building"],
+    "moji": "🏤"
+  },
+  "evergreen_tree": {
+    "unicode": "1F332",
+    "unicode_alternates": [],
+    "name": "evergreen tree",
+    "shortname": ":evergreen_tree:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "plant", "evergreen", "tree", "needles", "christmas"],
+    "moji": "🌲"
+  },
+  "exclamation": {
+    "unicode": "2757",
+    "unicode_alternates": ["2757-FE0F"],
+    "name": "heavy exclamation mark symbol",
+    "shortname": ":exclamation:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["surprise"],
+    "moji": "❗"
+  },
+  "expressionless": {
+    "unicode": "1F611",
+    "unicode_alternates": [],
+    "name": "expressionless face",
+    "shortname": ":expressionless:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": ["-_-", "-__-", "-___-"],
+    "keywords": ["expressionless", "blank", "void", "vapid", "without expression", "face", "indifferent"],
+    "moji": "😑"
+  },
+  "eye": {
+    "unicode": "1F441",
+    "unicode_alternates": [],
+    "name": "eye",
+    "shortname": ":eye:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["look", "peek", "watch"]
+  },
+  "eyeglasses": {
+    "unicode": "1F453",
+    "unicode_alternates": [],
+    "name": "eyeglasses",
+    "shortname": ":eyeglasses:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["accessories", "eyesight", "fashion", "eyeglasses", "spectacles", "eye", "sight", "nearsightedness", "myopia", "farsightedness", "hyperopia", "frames", "vision", "see", "blurry", "contacts"],
+    "moji": "👓"
+  },
+  "eyes": {
+    "unicode": "1F440",
+    "unicode_alternates": [],
+    "name": "eyes",
+    "shortname": ":eyes:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["look", "peek", "stalk", "watch"],
+    "moji": "👀"
+  },
+  "factory": {
+    "unicode": "1F3ED",
+    "unicode_alternates": [],
+    "name": "factory",
+    "shortname": ":factory:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building"],
+    "moji": "🏭"
+  },
+  "fallen_leaf": {
+    "unicode": "1F342",
+    "unicode_alternates": [],
+    "name": "fallen leaf",
+    "shortname": ":fallen_leaf:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["leaves", "nature", "plant", "vegetable", "leaf", "fall", "color", "deciduous", "autumn"],
+    "moji": "🍂"
+  },
+  "family": {
+    "unicode": "1F46A",
+    "unicode_alternates": [],
+    "name": "family",
+    "shortname": ":family:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["child", "dad", "father", "home", "mom", "mother", "parents", "family", "mother", "father", "child", "girl", "boy", "group", "unit"],
+    "moji": "👪"
+  },
+  "family_mmb": {
+    "unicode": "1F468-1F468-1F466",
+    "unicode_alternates": ["1F468-200D-1F468-200D-1F466"],
+    "name": "family (man,man,boy)",
+    "shortname": ":family_mmb:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["child", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "boy"]
+  },
+  "family_mmbb": {
+    "unicode": "1F468-1F468-1F466-1F466",
+    "unicode_alternates": ["1F468-200D-1F468-200D-1F466-200D-1F466"],
+    "name": "family (man,man,boy,boy)",
+    "shortname": ":family_mmbb:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["children", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "boy"]
+  },
+  "family_mmg": {
+    "unicode": "1F468-1F468-1F467",
+    "unicode_alternates": ["1F468-200D-1F468-200D-1F467"],
+    "name": "family (man,man,girl)",
+    "shortname": ":family_mmg:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["child", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "girl"]
+  },
+  "family_mmgb": {
+    "unicode": "1F468-1F468-1F467-1F466",
+    "unicode_alternates": ["1F468-200D-1F468-200D-1F467-200D-1F466"],
+    "name": "family (man,man,girl,boy)",
+    "shortname": ":family_mmgb:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["children", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "girl", "boy"]
+  },
+  "family_mmgg": {
+    "unicode": "1F468-1F468-1F467-1F467",
+    "unicode_alternates": ["1F468-200D-1F468-200D-1F467-200D-1F467"],
+    "name": "family (man,man,girl,girl)",
+    "shortname": ":family_mmgg:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["children", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "girl"]
+  },
+  "family_mwbb": {
+    "unicode": "1F468-1F469-1F466-1F466",
+    "unicode_alternates": ["1F468-200D-1F469-200D-1F466-200D-1F466"],
+    "name": "family (man,woman,boy,boy)",
+    "shortname": ":family_mwbb:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["dad", "father", "mom", "mother", "parents", "children", "boy", "group", "unit", "man", "woman"]
+  },
+  "family_mwg": {
+    "unicode": "1F468-1F469-1F467",
+    "unicode_alternates": ["1F468-200D-1F469-200D-1F467"],
+    "name": "family (man,woman,girl)",
+    "shortname": ":family_mwg:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["child", "dad", "father", "mom", "mother", "parents", "girl", "boy", "group", "unit", "man", "woman"]
+  },
+  "family_mwgb": {
+    "unicode": "1F468-1F469-1F467-1F466",
+    "unicode_alternates": ["1F468-200D-1F469-200D-1F467-200D-1F466"],
+    "name": "family (man,woman,girl,boy)",
+    "shortname": ":family_mwgb:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["dad", "father", "mom", "mother", "parents", "children", "girl", "boy", "group", "unit", "man", "woman"]
+  },
+  "family_mwgg": {
+    "unicode": "1F468-1F469-1F467-1F467",
+    "unicode_alternates": ["1F468-200D-1F469-200D-1F467-200D-1F467"],
+    "name": "family (man,woman,girl,girl)",
+    "shortname": ":family_mwgg:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["dad", "father", "mom", "mother", "parents", "children", "girl", "group", "unit", "man", "woman"]
+  },
+  "family_wwb": {
+    "unicode": "1F469-1F469-1F466",
+    "unicode_alternates": ["1F469-200D-1F469-200D-1F466"],
+    "name": "family (woman,woman,boy)",
+    "shortname": ":family_wwb:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["mom", "mother", "parents", "child", "boy", "group", "unit", "gay", "lesbian", "homosexual", "woman"]
+  },
+  "family_wwbb": {
+    "unicode": "1F469-1F469-1F466-1F466",
+    "unicode_alternates": ["1F469-200D-1F469-200D-1F466-200D-1F466"],
+    "name": "family (woman,woman,boy,boy)",
+    "shortname": ":family_wwbb:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["mom", "mother", "parents", "children", "group", "unit", "gay", "lesbian", "homosexual", "woman", "boy"]
+  },
+  "family_wwg": {
+    "unicode": "1F469-1F469-1F467",
+    "unicode_alternates": ["1F469-200D-1F469-200D-1F467"],
+    "name": "family (woman,woman,girl)",
+    "shortname": ":family_wwg:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["mom", "mother", "parents", "child", "woman", "girl", "group", "unit", "gay", "lesbian", "homosexual"]
+  },
+  "family_wwgb": {
+    "unicode": "1F469-1F469-1F467-1F466",
+    "unicode_alternates": ["1F469-200D-1F469-200D-1F467-200D-1F466"],
+    "name": "family (woman,woman,girl,boy)",
+    "shortname": ":family_wwgb:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["mom", "mother", "parents", "children", "group", "unit", "gay", "lesbian", "homosexual", "woman", "girl", "boy"]
+  },
+  "family_wwgg": {
+    "unicode": "1F469-1F469-1F467-1F467",
+    "unicode_alternates": ["1F469-200D-1F469-200D-1F467-200D-1F467"],
+    "name": "family (woman,woman,girl,girl)",
+    "shortname": ":family_wwgg:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["mom", "mother", "parents", "children", "group", "unit", "gay", "lesbian", "homosexual", "woman", "girl"]
+  },
+  "fast_forward": {
+    "unicode": "23E9",
+    "unicode_alternates": [],
+    "name": "black right-pointing double triangle",
+    "shortname": ":fast_forward:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square"],
+    "moji": "⏩"
+  },
+  "fax": {
+    "unicode": "1F4E0",
+    "unicode_alternates": [],
+    "name": "fax machine",
+    "shortname": ":fax:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["communication", "technology"],
+    "moji": "📠"
+  },
+  "fearful": {
+    "unicode": "1F628",
+    "unicode_alternates": [],
+    "name": "fearful face",
+    "shortname": ":fearful:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "nervous", "oops", "scared", "terrified", "fear", "fearful", "scared", "frightened"],
+    "moji": "😨"
+  },
+  "feet": {
+    "unicode": "1F43E",
+    "unicode_alternates": [],
+    "name": "paw prints",
+    "shortname": ":feet:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "cat", "dog", "footprints", "paw", "pet", "tracking", "paw", "prints", "mark", "imprints", "footsteps", "animal", "lion", "bear", "dog", "cat", "raccoon", "critter", "feet", "pawsteps"],
+    "moji": "🐾"
+  },
+  "ferris_wheel": {
+    "unicode": "1F3A1",
+    "unicode_alternates": [],
+    "name": "ferris wheel",
+    "shortname": ":ferris_wheel:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["carnival", "londoneye", "photo", "farris", "wheel", "amusement", "park", "fair", "ride", "entertainment"],
+    "moji": "🎡"
+  },
+  "file_cabinet": {
+    "unicode": "1F5C4",
+    "unicode_alternates": [],
+    "name": "file cabinet",
+    "shortname": ":file_cabinet:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["folders", "office", "documents", "storage"]
+  },
+  "file_folder": {
+    "unicode": "1F4C1",
+    "unicode_alternates": [],
+    "name": "file folder",
+    "shortname": ":file_folder:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["documents"],
+    "moji": "📁"
+  },
+  "film_frames": {
+    "unicode": "1F39E",
+    "unicode_alternates": [],
+    "name": "film frames",
+    "shortname": ":film_frames:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["movie", "record", "8mm", "16mm", "reel", "celluloid"]
+  },
+  "finger_pointing_down": {
+    "unicode": "1F597",
+    "unicode_alternates": [],
+    "name": "white down pointing left hand index",
+    "shortname": ":finger_pointing_down:",
+    "category": "people",
+    "aliases": [":white_down_pointing_left_hand_index:"],
+    "aliases_ascii": [],
+    "keywords": ["direction", "finger", "hand"]
+  },
+  "finger_pointing_down2": {
+    "unicode": "1F59F",
+    "unicode_alternates": [],
+    "name": "sideways white down pointing index",
+    "shortname": ":finger_pointing_down2:",
+    "category": "people",
+    "aliases": [":sideways_white_down_pointing_index:"],
+    "aliases_ascii": [],
+    "keywords": ["direction", "finger", "hand"]
+  },
+  "finger_pointing_left": {
+    "unicode": "1F598",
+    "unicode_alternates": [],
+    "name": "sideways white left pointing index",
+    "shortname": ":finger_pointing_left:",
+    "category": "people",
+    "aliases": [":sideways_white_left_pointing_index:"],
+    "aliases_ascii": [],
+    "keywords": ["direction", "finger", "hand"]
+  },
+  "finger_pointing_right": {
+    "unicode": "1F599",
+    "unicode_alternates": [],
+    "name": "sideways white right pointing index",
+    "shortname": ":finger_pointing_right:",
+    "category": "people",
+    "aliases": [":sideways_white_right_pointing_index:"],
+    "aliases_ascii": [],
+    "keywords": ["direction", "finger", "hand"]
+  },
+  "finger_pointing_up": {
+    "unicode": "1F59E",
+    "unicode_alternates": [],
+    "name": "sideways white up pointing index",
+    "shortname": ":finger_pointing_up:",
+    "category": "people",
+    "aliases": [":sideways_white_up_pointing_index:"],
+    "aliases_ascii": [],
+    "keywords": ["direction", "finger", "hand"]
+  },
+  "fire": {
+    "unicode": "1F525",
+    "unicode_alternates": [],
+    "name": "fire",
+    "shortname": ":fire:",
+    "category": "emoticons",
+    "aliases": [":flame:"],
+    "aliases_ascii": [],
+    "keywords": ["cook", "hot", "flame"],
+    "moji": "🔥"
+  },
+  "fire_engine": {
+    "unicode": "1F692",
+    "unicode_alternates": [],
+    "name": "fire engine",
+    "shortname": ":fire_engine:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cars", "transportation", "vehicle", "fire", "fighter", "engine", "truck", "emergency", "medical"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["carnival", "congratulations", "festival", "photo", "fireworks", "independence", "celebration", "explosion", "july", "4th", "rocket", "sky", "idea", "excitement"],
+    "moji": "🎆"
+  },
+  "first_quarter_moon": {
+    "unicode": "1F313",
+    "unicode_alternates": [],
+    "name": "first quarter moon symbol",
+    "shortname": ":first_quarter_moon:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "moon", "quarter", "first", "sky", "night", "cheese", "phase"],
+    "moji": "🌓"
+  },
+  "first_quarter_moon_with_face": {
+    "unicode": "1F31B",
+    "unicode_alternates": [],
+    "name": "first quarter moon with face",
+    "shortname": ":first_quarter_moon_with_face:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "moon", "first", "quarter", "anthropomorphic", "face", "sky", "night", "cheese", "phase"],
+    "moji": "🌛"
+  },
+  "fish": {
+    "unicode": "1F41F",
+    "unicode_alternates": [],
+    "name": "fish",
+    "shortname": ":fish:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "food", "nature"],
+    "moji": "🐟"
+  },
+  "fish_cake": {
+    "unicode": "1F365",
+    "unicode_alternates": [],
+    "name": "fish cake with swirl design",
+    "shortname": ":fish_cake:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "fish", "cake", "kamboko", "swirl", "ramen", "noodles", "naruto"],
+    "moji": "🍥"
+  },
+  "fishing_pole_and_fish": {
+    "unicode": "1F3A3",
+    "unicode_alternates": [],
+    "name": "fishing pole and fish",
+    "shortname": ":fishing_pole_and_fish:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "hobby", "fish", "fishing", "pole"],
+    "moji": "🎣"
+  },
+  "fist": {
+    "unicode": "270A",
+    "unicode_alternates": [],
+    "name": "raised fist",
+    "shortname": ":fist:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fingers", "grasp", "hand"],
+    "moji": "✊"
+  },
+  "five": {
+    "moji": "5️⃣",
+    "unicode": "0035-20E3",
+    "unicode_alternates": ["0035-FE0F-20E3"],
+    "name": "digit five",
+    "shortname": ":five:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "numbers", "prime"]
+  },
+  "flag_ac": {
+    "unicode": "1F1E6-1F1E8",
+    "unicode_alternates": [],
+    "name": "ascension",
+    "shortname": ":flag_ac:",
+    "category": "flags",
+    "aliases": [":ac:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ac"]
+  },
+  "flag_ad": {
+    "unicode": "1F1E6-1F1E9",
+    "unicode_alternates": [],
+    "name": "andorra",
+    "shortname": ":flag_ad:",
+    "category": "flags",
+    "aliases": [":ad:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ad"]
+  },
+  "flag_ae": {
+    "unicode": "1F1E6-1F1EA",
+    "unicode_alternates": [],
+    "name": "the united arab emirates",
+    "shortname": ":flag_ae:",
+    "category": "flags",
+    "aliases": [":ae:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ae"]
+  },
+  "flag_af": {
+    "unicode": "1F1E6-1F1EB",
+    "unicode_alternates": [],
+    "name": "afghanistan",
+    "shortname": ":flag_af:",
+    "category": "flags",
+    "aliases": [":af:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "afghanestan", "af"]
+  },
+  "flag_ag": {
+    "unicode": "1F1E6-1F1EC",
+    "unicode_alternates": [],
+    "name": "antigua and barbuda",
+    "shortname": ":flag_ag:",
+    "category": "flags",
+    "aliases": [":ag:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ag"]
+  },
+  "flag_ai": {
+    "unicode": "1F1E6-1F1EE",
+    "unicode_alternates": [],
+    "name": "anguilla",
+    "shortname": ":flag_ai:",
+    "category": "flags",
+    "aliases": [":ai:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ai"]
+  },
+  "flag_al": {
+    "unicode": "1F1E6-1F1F1",
+    "unicode_alternates": [],
+    "name": "albania",
+    "shortname": ":flag_al:",
+    "category": "flags",
+    "aliases": [":al:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "shqiperia", "al"]
+  },
+  "flag_am": {
+    "unicode": "1F1E6-1F1F2",
+    "unicode_alternates": [],
+    "name": "armenia",
+    "shortname": ":flag_am:",
+    "category": "flags",
+    "aliases": [":am:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "hayastan", "am"]
+  },
+  "flag_ao": {
+    "unicode": "1F1E6-1F1F4",
+    "unicode_alternates": [],
+    "name": "angola",
+    "shortname": ":flag_ao:",
+    "category": "flags",
+    "aliases": [":ao:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ao"]
+  },
+  "flag_ar": {
+    "unicode": "1F1E6-1F1F7",
+    "unicode_alternates": [],
+    "name": "argentina",
+    "shortname": ":flag_ar:",
+    "category": "flags",
+    "aliases": [":ar:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ar"]
+  },
+  "flag_at": {
+    "unicode": "1F1E6-1F1F9",
+    "unicode_alternates": [],
+    "name": "austria",
+    "shortname": ":flag_at:",
+    "category": "flags",
+    "aliases": [":at:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "&ouml;sterreich", "osterreich", "at"]
+  },
+  "flag_au": {
+    "unicode": "1F1E6-1F1FA",
+    "unicode_alternates": [],
+    "name": "australia",
+    "shortname": ":flag_au:",
+    "category": "flags",
+    "aliases": [":au:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "au"]
+  },
+  "flag_aw": {
+    "unicode": "1F1E6-1F1FC",
+    "unicode_alternates": [],
+    "name": "aruba",
+    "shortname": ":flag_aw:",
+    "category": "flags",
+    "aliases": [":aw:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "aw"]
+  },
+  "flag_az": {
+    "unicode": "1F1E6-1F1FF",
+    "unicode_alternates": [],
+    "name": "azerbaijan",
+    "shortname": ":flag_az:",
+    "category": "flags",
+    "aliases": [":az:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "azarbaycan", "az"]
+  },
+  "flag_ba": {
+    "unicode": "1F1E7-1F1E6",
+    "unicode_alternates": [],
+    "name": "bosnia and herzegovina",
+    "shortname": ":flag_ba:",
+    "category": "flags",
+    "aliases": [":ba:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bosna i hercegovina", "ba"]
+  },
+  "flag_bb": {
+    "unicode": "1F1E7-1F1E7",
+    "unicode_alternates": [],
+    "name": "barbados",
+    "shortname": ":flag_bb:",
+    "category": "flags",
+    "aliases": [":bb:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bb"]
+  },
+  "flag_bd": {
+    "unicode": "1F1E7-1F1E9",
+    "unicode_alternates": [],
+    "name": "bangladesh",
+    "shortname": ":flag_bd:",
+    "category": "flags",
+    "aliases": [":bd:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bd"]
+  },
+  "flag_be": {
+    "unicode": "1F1E7-1F1EA",
+    "unicode_alternates": [],
+    "name": "belgium",
+    "shortname": ":flag_be:",
+    "category": "flags",
+    "aliases": [":be:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "belgique", "belgie", "be"]
+  },
+  "flag_bf": {
+    "unicode": "1F1E7-1F1EB",
+    "unicode_alternates": [],
+    "name": "burkina faso",
+    "shortname": ":flag_bf:",
+    "category": "flags",
+    "aliases": [":bf:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bf"]
+  },
+  "flag_bg": {
+    "unicode": "1F1E7-1F1EC",
+    "unicode_alternates": [],
+    "name": "bulgaria",
+    "shortname": ":flag_bg:",
+    "category": "flags",
+    "aliases": [":bg:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bg"]
+  },
+  "flag_bh": {
+    "unicode": "1F1E7-1F1ED",
+    "unicode_alternates": [],
+    "name": "bahrain",
+    "shortname": ":flag_bh:",
+    "category": "flags",
+    "aliases": [":bh:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "al bahrayn", "bh"]
+  },
+  "flag_bi": {
+    "unicode": "1F1E7-1F1EE",
+    "unicode_alternates": [],
+    "name": "burundi",
+    "shortname": ":flag_bi:",
+    "category": "flags",
+    "aliases": [":bi:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bi"]
+  },
+  "flag_bj": {
+    "unicode": "1F1E7-1F1EF",
+    "unicode_alternates": [],
+    "name": "benin",
+    "shortname": ":flag_bj:",
+    "category": "flags",
+    "aliases": [":bj:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bj"]
+  },
+  "flag_black": {
+    "unicode": "1F3F4",
+    "unicode_alternates": [],
+    "name": "waving black flag",
+    "shortname": ":flag_black:",
+    "category": "objects_symbols",
+    "aliases": [":waving_black_flag:"],
+    "aliases_ascii": [],
+    "keywords": ["symbol", "signal"]
+  },
+  "flag_bm": {
+    "unicode": "1F1E7-1F1F2",
+    "unicode_alternates": [],
+    "name": "bermuda",
+    "shortname": ":flag_bm:",
+    "category": "flags",
+    "aliases": [":bm:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bm"]
+  },
+  "flag_bn": {
+    "unicode": "1F1E7-1F1F3",
+    "unicode_alternates": [],
+    "name": "brunei",
+    "shortname": ":flag_bn:",
+    "category": "flags",
+    "aliases": [":bn:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bn"]
+  },
+  "flag_bo": {
+    "unicode": "1F1E7-1F1F4",
+    "unicode_alternates": [],
+    "name": "bolivia",
+    "shortname": ":flag_bo:",
+    "category": "flags",
+    "aliases": [":bo:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bo"]
+  },
+  "flag_br": {
+    "unicode": "1F1E7-1F1F7",
+    "unicode_alternates": [],
+    "name": "brazil",
+    "shortname": ":flag_br:",
+    "category": "flags",
+    "aliases": [":br:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "brasil", "br"]
+  },
+  "flag_bs": {
+    "unicode": "1F1E7-1F1F8",
+    "unicode_alternates": [],
+    "name": "the bahamas",
+    "shortname": ":flag_bs:",
+    "category": "flags",
+    "aliases": [":bs:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bs"]
+  },
+  "flag_bt": {
+    "unicode": "1F1E7-1F1F9",
+    "unicode_alternates": [],
+    "name": "bhutan",
+    "shortname": ":flag_bt:",
+    "category": "flags",
+    "aliases": [":bt:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bt"]
+  },
+  "flag_bw": {
+    "unicode": "1F1E7-1F1FC",
+    "unicode_alternates": [],
+    "name": "botswana",
+    "shortname": ":flag_bw:",
+    "category": "flags",
+    "aliases": [":bw:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bw"]
+  },
+  "flag_by": {
+    "unicode": "1F1E7-1F1FE",
+    "unicode_alternates": [],
+    "name": "belarus",
+    "shortname": ":flag_by:",
+    "category": "flags",
+    "aliases": [":by:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "byelarus", "by"]
+  },
+  "flag_bz": {
+    "unicode": "1F1E7-1F1FF",
+    "unicode_alternates": [],
+    "name": "belize",
+    "shortname": ":flag_bz:",
+    "category": "flags",
+    "aliases": [":bz:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bz"]
+  },
+  "flag_ca": {
+    "unicode": "1F1E8-1F1E6",
+    "unicode_alternates": [],
+    "name": "canada",
+    "shortname": ":flag_ca:",
+    "category": "flags",
+    "aliases": [":ca:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ca"]
+  },
+  "flag_cd": {
+    "unicode": "1F1E8-1F1E9",
+    "unicode_alternates": [],
+    "name": "the democratic republic of the congo",
+    "shortname": ":flag_cd:",
+    "category": "flags",
+    "aliases": [":congo:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "r&eacute;publique d&eacute;mocratique du congo", "republique democratique du congo", "cd"]
+  },
+  "flag_cf": {
+    "unicode": "1F1E8-1F1EB",
+    "unicode_alternates": [],
+    "name": "central african republic",
+    "shortname": ":flag_cf:",
+    "category": "flags",
+    "aliases": [":cf:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "cf"]
+  },
+  "flag_cg": {
+    "unicode": "1F1E8-1F1EC",
+    "unicode_alternates": [],
+    "name": "the republic of the congo",
+    "shortname": ":flag_cg:",
+    "category": "flags",
+    "aliases": [":cg:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "cg"]
+  },
+  "flag_ch": {
+    "unicode": "1F1E8-1F1ED",
+    "unicode_alternates": [],
+    "name": "switzerland",
+    "shortname": ":flag_ch:",
+    "category": "flags",
+    "aliases": [":ch:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "swiss"]
+  },
+  "flag_ci": {
+    "unicode": "1F1E8-1F1EE",
+    "unicode_alternates": [],
+    "name": "cote d'ivoire",
+    "shortname": ":flag_ci:",
+    "category": "flags",
+    "aliases": [":ci:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ci"]
+  },
+  "flag_cl": {
+    "unicode": "1F1E8-1F1F1",
+    "unicode_alternates": [],
+    "name": "chile",
+    "shortname": ":flag_cl:",
+    "category": "flags",
+    "aliases": [":chile:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "cl"]
+  },
+  "flag_cm": {
+    "unicode": "1F1E8-1F1F2",
+    "unicode_alternates": [],
+    "name": "cameroon",
+    "shortname": ":flag_cm:",
+    "category": "flags",
+    "aliases": [":cm:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "cm"]
+  },
+  "flag_cn": {
+    "unicode": "1F1E8-1F1F3",
+    "unicode_alternates": [],
+    "name": "china",
+    "shortname": ":flag_cn:",
+    "category": "flags",
+    "aliases": [":cn:"],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "prc", "zhong guo", "country", "nation", "cn"]
+  },
+  "flag_co": {
+    "unicode": "1F1E8-1F1F4",
+    "unicode_alternates": [],
+    "name": "colombia",
+    "shortname": ":flag_co:",
+    "category": "flags",
+    "aliases": [":co:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "co"]
+  },
+  "flag_cr": {
+    "unicode": "1F1E8-1F1F7",
+    "unicode_alternates": [],
+    "name": "costa rica",
+    "shortname": ":flag_cr:",
+    "category": "flags",
+    "aliases": [":cr:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "cr"]
+  },
+  "flag_cu": {
+    "unicode": "1F1E8-1F1FA",
+    "unicode_alternates": [],
+    "name": "cuba",
+    "shortname": ":flag_cu:",
+    "category": "flags",
+    "aliases": [":cu:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "cu"]
+  },
+  "flag_cv": {
+    "unicode": "1F1E8-1F1FB",
+    "unicode_alternates": [],
+    "name": "cape verde",
+    "shortname": ":flag_cv:",
+    "category": "flags",
+    "aliases": [":cv:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "cabo verde", "cv"]
+  },
+  "flag_cy": {
+    "unicode": "1F1E8-1F1FE",
+    "unicode_alternates": [],
+    "name": "cyprus",
+    "shortname": ":flag_cy:",
+    "category": "flags",
+    "aliases": [":cy:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "kibris", "kypros", "cy"]
+  },
+  "flag_cz": {
+    "unicode": "1F1E8-1F1FF",
+    "unicode_alternates": [],
+    "name": "the czech republic",
+    "shortname": ":flag_cz:",
+    "category": "flags",
+    "aliases": [":cz:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ceska republika", "cz"]
+  },
+  "flag_de": {
+    "unicode": "1F1E9-1F1EA",
+    "unicode_alternates": [],
+    "name": "germany",
+    "shortname": ":flag_de:",
+    "category": "flags",
+    "aliases": [":de:"],
+    "aliases_ascii": [],
+    "keywords": ["german", "nation", "deutschland", "country", "de"]
+  },
+  "flag_dj": {
+    "unicode": "1F1E9-1F1EF",
+    "unicode_alternates": [],
+    "name": "djibouti",
+    "shortname": ":flag_dj:",
+    "category": "flags",
+    "aliases": [":dj:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "dj"]
+  },
+  "flag_dk": {
+    "unicode": "1F1E9-1F1F0",
+    "unicode_alternates": [],
+    "name": "denmark",
+    "shortname": ":flag_dk:",
+    "category": "flags",
+    "aliases": [":dk:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "danmark", "dk"]
+  },
+  "flag_dm": {
+    "unicode": "1F1E9-1F1F2",
+    "unicode_alternates": [],
+    "name": "dominica",
+    "shortname": ":flag_dm:",
+    "category": "flags",
+    "aliases": [":dm:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "dm"]
+  },
+  "flag_do": {
+    "unicode": "1F1E9-1F1F4",
+    "unicode_alternates": [],
+    "name": "the dominican republic",
+    "shortname": ":flag_do:",
+    "category": "flags",
+    "aliases": [":do:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "do"]
+  },
+  "flag_dz": {
+    "unicode": "1F1E9-1F1FF",
+    "unicode_alternates": [],
+    "name": "algeria",
+    "shortname": ":flag_dz:",
+    "category": "flags",
+    "aliases": [":dz:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "al jaza'ir", "al jazair", "dz"]
+  },
+  "flag_ec": {
+    "unicode": "1F1EA-1F1E8",
+    "unicode_alternates": [],
+    "name": "ecuador",
+    "shortname": ":flag_ec:",
+    "category": "flags",
+    "aliases": [":ec:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ec"]
+  },
+  "flag_ee": {
+    "unicode": "1F1EA-1F1EA",
+    "unicode_alternates": [],
+    "name": "estonia",
+    "shortname": ":flag_ee:",
+    "category": "flags",
+    "aliases": [":ee:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "eesti vabariik", "ee"]
+  },
+  "flag_eg": {
+    "unicode": "1F1EA-1F1EC",
+    "unicode_alternates": [],
+    "name": "egypt",
+    "shortname": ":flag_eg:",
+    "category": "flags",
+    "aliases": [":eg:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "misr", "eg"]
+  },
+  "flag_eh": {
+    "unicode": "1F1EA-1F1ED",
+    "unicode_alternates": [],
+    "name": "western sahara",
+    "shortname": ":flag_eh:",
+    "category": "flags",
+    "aliases": [":eh:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "aṣ-Ṣaḥrā’ al-gharbīyah", "sahra", "gharbiyah", "eh"]
+  },
+  "flag_er": {
+    "unicode": "1F1EA-1F1F7",
+    "unicode_alternates": [],
+    "name": "eritrea",
+    "shortname": ":flag_er:",
+    "category": "flags",
+    "aliases": [":er:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "hagere ertra", "er"]
+  },
+  "flag_es": {
+    "unicode": "1F1EA-1F1F8",
+    "unicode_alternates": [],
+    "name": "spain",
+    "shortname": ":flag_es:",
+    "category": "flags",
+    "aliases": [":es:"],
+    "aliases_ascii": [],
+    "keywords": ["nation", "espa&ntilde;a", "country", "espana", "es"]
+  },
+  "flag_et": {
+    "unicode": "1F1EA-1F1F9",
+    "unicode_alternates": [],
+    "name": "ethiopia",
+    "shortname": ":flag_et:",
+    "category": "flags",
+    "aliases": [":et:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ityop'iya", "ityopiya", "et"]
+  },
+  "flag_fi": {
+    "unicode": "1F1EB-1F1EE",
+    "unicode_alternates": [],
+    "name": "finland",
+    "shortname": ":flag_fi:",
+    "category": "flags",
+    "aliases": [":fi:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "suomen tasavalta", "fi"]
+  },
+  "flag_fj": {
+    "unicode": "1F1EB-1F1EF",
+    "unicode_alternates": [],
+    "name": "fiji",
+    "shortname": ":flag_fj:",
+    "category": "flags",
+    "aliases": [":fj:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "fj"]
+  },
+  "flag_fk": {
+    "unicode": "1F1EB-1F1F0",
+    "unicode_alternates": [],
+    "name": "falkland islands",
+    "shortname": ":flag_fk:",
+    "category": "flags",
+    "aliases": [":fk:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "islas malvinas", "fk"]
+  },
+  "flag_fm": {
+    "unicode": "1F1EB-1F1F2",
+    "unicode_alternates": [],
+    "name": "micronesia",
+    "shortname": ":flag_fm:",
+    "category": "flags",
+    "aliases": [":fm:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "fm"]
+  },
+  "flag_fo": {
+    "unicode": "1F1EB-1F1F4",
+    "unicode_alternates": [],
+    "name": "faroe islands",
+    "shortname": ":flag_fo:",
+    "category": "flags",
+    "aliases": [":fo:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "foroyar", "fo"]
+  },
+  "flag_fr": {
+    "unicode": "1F1EB-1F1F7",
+    "unicode_alternates": [],
+    "name": "france",
+    "shortname": ":flag_fr:",
+    "category": "flags",
+    "aliases": [":fr:"],
+    "aliases_ascii": [],
+    "keywords": ["french", "nation", "country", "fr"]
+  },
+  "flag_ga": {
+    "unicode": "1F1EC-1F1E6",
+    "unicode_alternates": [],
+    "name": "gabon",
+    "shortname": ":flag_ga:",
+    "category": "flags",
+    "aliases": [":ga:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ga"]
+  },
+  "flag_gb": {
+    "unicode": "1F1EC-1F1E7",
+    "unicode_alternates": [],
+    "name": "great britain",
+    "shortname": ":flag_gb:",
+    "category": "flags",
+    "aliases": [":gb:"],
+    "aliases_ascii": [],
+    "keywords": ["UK", "gb", "britsh", "nation", "united kingdom", "england", "country"]
+  },
+  "flag_gd": {
+    "unicode": "1F1EC-1F1E9",
+    "unicode_alternates": [],
+    "name": "grenada",
+    "shortname": ":flag_gd:",
+    "category": "flags",
+    "aliases": [":gd:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "gd"]
+  },
+  "flag_ge": {
+    "unicode": "1F1EC-1F1EA",
+    "unicode_alternates": [],
+    "name": "georgia",
+    "shortname": ":flag_ge:",
+    "category": "flags",
+    "aliases": [":ge:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sak'art'velo", "sakartvelo", "ge"]
+  },
+  "flag_gh": {
+    "unicode": "1F1EC-1F1ED",
+    "unicode_alternates": [],
+    "name": "ghana",
+    "shortname": ":flag_gh:",
+    "category": "flags",
+    "aliases": [":gh:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "gh"]
+  },
+  "flag_gi": {
+    "unicode": "1F1EC-1F1EE",
+    "unicode_alternates": [],
+    "name": "gibraltar",
+    "shortname": ":flag_gi:",
+    "category": "flags",
+    "aliases": [":gi:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "gi"]
+  },
+  "flag_gl": {
+    "unicode": "1F1EC-1F1F1",
+    "unicode_alternates": [],
+    "name": "greenland",
+    "shortname": ":flag_gl:",
+    "category": "flags",
+    "aliases": [":gl:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "kalaallit nunaat", "gl"]
+  },
+  "flag_gm": {
+    "unicode": "1F1EC-1F1F2",
+    "unicode_alternates": [],
+    "name": "the gambia",
+    "shortname": ":flag_gm:",
+    "category": "flags",
+    "aliases": [":gm:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "gm"]
+  },
+  "flag_gn": {
+    "unicode": "1F1EC-1F1F3",
+    "unicode_alternates": [],
+    "name": "guinea",
+    "shortname": ":flag_gn:",
+    "category": "flags",
+    "aliases": [":gn:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "guinee", "gn"]
+  },
+  "flag_gq": {
+    "unicode": "1F1EC-1F1F6",
+    "unicode_alternates": [],
+    "name": "equatorial guinea",
+    "shortname": ":flag_gq:",
+    "category": "flags",
+    "aliases": [":gq:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "guinea ecuatorial", "gq"]
+  },
+  "flag_gr": {
+    "unicode": "1F1EC-1F1F7",
+    "unicode_alternates": [],
+    "name": "greece",
+    "shortname": ":flag_gr:",
+    "category": "flags",
+    "aliases": [":gr:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ellas", "ellada", "gr"]
+  },
+  "flag_gt": {
+    "unicode": "1F1EC-1F1F9",
+    "unicode_alternates": [],
+    "name": "guatemala",
+    "shortname": ":flag_gt:",
+    "category": "flags",
+    "aliases": [":gt:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "gt"]
+  },
+  "flag_gu": {
+    "unicode": "1F1EC-1F1FA",
+    "unicode_alternates": [],
+    "name": "guam",
+    "shortname": ":flag_gu:",
+    "category": "flags",
+    "aliases": [":gu:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "gu"]
+  },
+  "flag_gw": {
+    "unicode": "1F1EC-1F1FC",
+    "unicode_alternates": [],
+    "name": "guinea-bissau",
+    "shortname": ":flag_gw:",
+    "category": "flags",
+    "aliases": [":gw:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "guine-bissau", "guine bissau", "gw"]
+  },
+  "flag_gy": {
+    "unicode": "1F1EC-1F1FE",
+    "unicode_alternates": [],
+    "name": "guyana",
+    "shortname": ":flag_gy:",
+    "category": "flags",
+    "aliases": [":gy:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "gy"]
+  },
+  "flag_hk": {
+    "unicode": "1F1ED-1F1F0",
+    "unicode_alternates": [],
+    "name": "hong kong",
+    "shortname": ":flag_hk:",
+    "category": "flags",
+    "aliases": [":hk:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "xianggang", "hk"]
+  },
+  "flag_hn": {
+    "unicode": "1F1ED-1F1F3",
+    "unicode_alternates": [],
+    "name": "honduras",
+    "shortname": ":flag_hn:",
+    "category": "flags",
+    "aliases": [":hn:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "hn"]
+  },
+  "flag_hr": {
+    "unicode": "1F1ED-1F1F7",
+    "unicode_alternates": [],
+    "name": "croatia",
+    "shortname": ":flag_hr:",
+    "category": "flags",
+    "aliases": [":hr:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "hrvatska", "hr"]
+  },
+  "flag_ht": {
+    "unicode": "1F1ED-1F1F9",
+    "unicode_alternates": [],
+    "name": "haiti",
+    "shortname": ":flag_ht:",
+    "category": "flags",
+    "aliases": [":ht:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ht"]
+  },
+  "flag_hu": {
+    "unicode": "1F1ED-1F1FA",
+    "unicode_alternates": [],
+    "name": "hungary",
+    "shortname": ":flag_hu:",
+    "category": "flags",
+    "aliases": [":hu:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "magyarorszag", "hu"]
+  },
+  "flag_id": {
+    "unicode": "1F1EE-1F1E9",
+    "unicode_alternates": [],
+    "name": "indonesia",
+    "shortname": ":flag_id:",
+    "category": "flags",
+    "aliases": [":indonesia:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "id"]
+  },
+  "flag_ie": {
+    "unicode": "1F1EE-1F1EA",
+    "unicode_alternates": [],
+    "name": "ireland",
+    "shortname": ":flag_ie:",
+    "category": "flags",
+    "aliases": [":ie:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "&eacute;ire", "eire", "ie"]
+  },
+  "flag_il": {
+    "unicode": "1F1EE-1F1F1",
+    "unicode_alternates": [],
+    "name": "israel",
+    "shortname": ":flag_il:",
+    "category": "flags",
+    "aliases": [":il:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "yisra'el", "yisrael", "il"]
+  },
+  "flag_in": {
+    "unicode": "1F1EE-1F1F3",
+    "unicode_alternates": [],
+    "name": "india",
+    "shortname": ":flag_in:",
+    "category": "flags",
+    "aliases": [":in:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "bharat", "in"]
+  },
+  "flag_iq": {
+    "unicode": "1F1EE-1F1F6",
+    "unicode_alternates": [],
+    "name": "iraq",
+    "shortname": ":flag_iq:",
+    "category": "flags",
+    "aliases": [":iq:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "iq"]
+  },
+  "flag_ir": {
+    "unicode": "1F1EE-1F1F7",
+    "unicode_alternates": [],
+    "name": "iran",
+    "shortname": ":flag_ir:",
+    "category": "flags",
+    "aliases": [":ir:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ir"]
+  },
+  "flag_is": {
+    "unicode": "1F1EE-1F1F8",
+    "unicode_alternates": [],
+    "name": "iceland",
+    "shortname": ":flag_is:",
+    "category": "flags",
+    "aliases": [":is:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "lyoveldio island", "is"]
+  },
+  "flag_it": {
+    "unicode": "1F1EE-1F1F9",
+    "unicode_alternates": [],
+    "name": "italy",
+    "shortname": ":flag_it:",
+    "category": "flags",
+    "aliases": [":it:"],
+    "aliases_ascii": [],
+    "keywords": ["italia", "country", "nation", "it"]
+  },
+  "flag_je": {
+    "unicode": "1F1EF-1F1EA",
+    "unicode_alternates": [],
+    "name": "jersey",
+    "shortname": ":flag_je:",
+    "category": "flags",
+    "aliases": [":je:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "je"]
+  },
+  "flag_jm": {
+    "unicode": "1F1EF-1F1F2",
+    "unicode_alternates": [],
+    "name": "jamaica",
+    "shortname": ":flag_jm:",
+    "category": "flags",
+    "aliases": [":jm:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "jm"]
+  },
+  "flag_jo": {
+    "unicode": "1F1EF-1F1F4",
+    "unicode_alternates": [],
+    "name": "jordan",
+    "shortname": ":flag_jo:",
+    "category": "flags",
+    "aliases": [":jo:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "al urdun", "jo"]
+  },
+  "flag_jp": {
+    "unicode": "1F1EF-1F1F5",
+    "unicode_alternates": [],
+    "name": "japan",
+    "shortname": ":flag_jp:",
+    "category": "flags",
+    "aliases": [":jp:"],
+    "aliases_ascii": [],
+    "keywords": ["nation", "nippon", "country", "jp"]
+  },
+  "flag_ke": {
+    "unicode": "1F1F0-1F1EA",
+    "unicode_alternates": [],
+    "name": "kenya",
+    "shortname": ":flag_ke:",
+    "category": "flags",
+    "aliases": [":ke:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ke"]
+  },
+  "flag_kg": {
+    "unicode": "1F1F0-1F1EC",
+    "unicode_alternates": [],
+    "name": "kyrgyzstan",
+    "shortname": ":flag_kg:",
+    "category": "flags",
+    "aliases": [":kg:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "kyrgyz respublikasy", "kg"]
+  },
+  "flag_kh": {
+    "unicode": "1F1F0-1F1ED",
+    "unicode_alternates": [],
+    "name": "cambodia",
+    "shortname": ":flag_kh:",
+    "category": "flags",
+    "aliases": [":kh:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "kampuchea", "kh"]
+  },
+  "flag_ki": {
+    "unicode": "1F1F0-1F1EE",
+    "unicode_alternates": [],
+    "name": "kiribati",
+    "shortname": ":flag_ki:",
+    "category": "flags",
+    "aliases": [":ki:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "kiribati", "kiribas", "ki"]
+  },
+  "flag_km": {
+    "unicode": "1F1F0-1F1F2",
+    "unicode_alternates": [],
+    "name": "the comoros",
+    "shortname": ":flag_km:",
+    "category": "flags",
+    "aliases": [":km:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "km"]
+  },
+  "flag_kn": {
+    "unicode": "1F1F0-1F1F3",
+    "unicode_alternates": [],
+    "name": "saint kitts and nevis",
+    "shortname": ":flag_kn:",
+    "category": "flags",
+    "aliases": [":kn:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "kn"]
+  },
+  "flag_kp": {
+    "unicode": "1F1F0-1F1F5",
+    "unicode_alternates": [],
+    "name": "north korea",
+    "shortname": ":flag_kp:",
+    "category": "flags",
+    "aliases": [":kp:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "kp"]
+  },
+  "flag_kr": {
+    "unicode": "1F1F0-1F1F7",
+    "unicode_alternates": [],
+    "name": "korea",
+    "shortname": ":flag_kr:",
+    "category": "flags",
+    "aliases": [":kr:"],
+    "aliases_ascii": [],
+    "keywords": ["nation", "country", "south korea", "kr"]
+  },
+  "flag_kw": {
+    "unicode": "1F1F0-1F1FC",
+    "unicode_alternates": [],
+    "name": "kuwait",
+    "shortname": ":flag_kw:",
+    "category": "flags",
+    "aliases": [":kw:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "al kuwayt", "kw"]
+  },
+  "flag_ky": {
+    "unicode": "1F1F0-1F1FE",
+    "unicode_alternates": [],
+    "name": "cayman islands",
+    "shortname": ":flag_ky:",
+    "category": "flags",
+    "aliases": [":ky:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ky"]
+  },
+  "flag_kz": {
+    "unicode": "1F1F0-1F1FF",
+    "unicode_alternates": [],
+    "name": "kazakhstan",
+    "shortname": ":flag_kz:",
+    "category": "flags",
+    "aliases": [":kz:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "qazaqstan", "kz"]
+  },
+  "flag_la": {
+    "unicode": "1F1F1-1F1E6",
+    "unicode_alternates": [],
+    "name": "laos",
+    "shortname": ":flag_la:",
+    "category": "flags",
+    "aliases": [":la:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "la"]
+  },
+  "flag_lb": {
+    "unicode": "1F1F1-1F1E7",
+    "unicode_alternates": [],
+    "name": "lebanon",
+    "shortname": ":flag_lb:",
+    "category": "flags",
+    "aliases": [":lb:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "lubnan", "lb"]
+  },
+  "flag_lc": {
+    "unicode": "1F1F1-1F1E8",
+    "unicode_alternates": [],
+    "name": "saint lucia",
+    "shortname": ":flag_lc:",
+    "category": "flags",
+    "aliases": [":lc:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "lc"]
+  },
+  "flag_li": {
+    "unicode": "1F1F1-1F1EE",
+    "unicode_alternates": [],
+    "name": "liechtenstein",
+    "shortname": ":flag_li:",
+    "category": "flags",
+    "aliases": [":li:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "li"]
+  },
+  "flag_lk": {
+    "unicode": "1F1F1-1F1F0",
+    "unicode_alternates": [],
+    "name": "sri lanka",
+    "shortname": ":flag_lk:",
+    "category": "flags",
+    "aliases": [":lk:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "lk"]
+  },
+  "flag_lr": {
+    "unicode": "1F1F1-1F1F7",
+    "unicode_alternates": [],
+    "name": "liberia",
+    "shortname": ":flag_lr:",
+    "category": "flags",
+    "aliases": [":lr:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "lr"]
+  },
+  "flag_ls": {
+    "unicode": "1F1F1-1F1F8",
+    "unicode_alternates": [],
+    "name": "lesotho",
+    "shortname": ":flag_ls:",
+    "category": "flags",
+    "aliases": [":ls:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ls"]
+  },
+  "flag_lt": {
+    "unicode": "1F1F1-1F1F9",
+    "unicode_alternates": [],
+    "name": "lithuania",
+    "shortname": ":flag_lt:",
+    "category": "flags",
+    "aliases": [":lt:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "lietuva", "lt"]
+  },
+  "flag_lu": {
+    "unicode": "1F1F1-1F1FA",
+    "unicode_alternates": [],
+    "name": "luxembourg",
+    "shortname": ":flag_lu:",
+    "category": "flags",
+    "aliases": [":lu:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "luxembourg", "letzebuerg", "lu"]
+  },
+  "flag_lv": {
+    "unicode": "1F1F1-1F1FB",
+    "unicode_alternates": [],
+    "name": "latvia",
+    "shortname": ":flag_lv:",
+    "category": "flags",
+    "aliases": [":lv:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "latvija", "lv"]
+  },
+  "flag_ly": {
+    "unicode": "1F1F1-1F1FE",
+    "unicode_alternates": [],
+    "name": "libya",
+    "shortname": ":flag_ly:",
+    "category": "flags",
+    "aliases": [":ly:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "libiyah", "ly"]
+  },
+  "flag_ma": {
+    "unicode": "1F1F2-1F1E6",
+    "unicode_alternates": [],
+    "name": "morocco",
+    "shortname": ":flag_ma:",
+    "category": "flags",
+    "aliases": [":ma:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "al maghrib", "ma"]
+  },
+  "flag_mc": {
+    "unicode": "1F1F2-1F1E8",
+    "unicode_alternates": [],
+    "name": "monaco",
+    "shortname": ":flag_mc:",
+    "category": "flags",
+    "aliases": [":mc:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "mc"]
+  },
+  "flag_md": {
+    "unicode": "1F1F2-1F1E9",
+    "unicode_alternates": [],
+    "name": "moldova",
+    "shortname": ":flag_md:",
+    "category": "flags",
+    "aliases": [":md:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "md"]
+  },
+  "flag_me": {
+    "unicode": "1F1F2-1F1EA",
+    "unicode_alternates": [],
+    "name": "montenegro",
+    "shortname": ":flag_me:",
+    "category": "flags",
+    "aliases": [":me:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "crna gora", "me"]
+  },
+  "flag_mg": {
+    "unicode": "1F1F2-1F1EC",
+    "unicode_alternates": [],
+    "name": "madagascar",
+    "shortname": ":flag_mg:",
+    "category": "flags",
+    "aliases": [":mg:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "mg"]
+  },
+  "flag_mh": {
+    "unicode": "1F1F2-1F1ED",
+    "unicode_alternates": [],
+    "name": "the marshall islands",
+    "shortname": ":flag_mh:",
+    "category": "flags",
+    "aliases": [":mh:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "mh"]
+  },
+  "flag_mk": {
+    "unicode": "1F1F2-1F1F0",
+    "unicode_alternates": [],
+    "name": "macedonia",
+    "shortname": ":flag_mk:",
+    "category": "flags",
+    "aliases": [":mk:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "mk"]
+  },
+  "flag_ml": {
+    "unicode": "1F1F2-1F1F1",
+    "unicode_alternates": [],
+    "name": "mali",
+    "shortname": ":flag_ml:",
+    "category": "flags",
+    "aliases": [":ml:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ml"]
+  },
+  "flag_mm": {
+    "unicode": "1F1F2-1F1F2",
+    "unicode_alternates": [],
+    "name": "myanmar",
+    "shortname": ":flag_mm:",
+    "category": "flags",
+    "aliases": [":mm:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "myanma naingngandaw", "mm"]
+  },
+  "flag_mn": {
+    "unicode": "1F1F2-1F1F3",
+    "unicode_alternates": [],
+    "name": "mongolia",
+    "shortname": ":flag_mn:",
+    "category": "flags",
+    "aliases": [":mn:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "mongol uls", "mn"]
+  },
+  "flag_mo": {
+    "unicode": "1F1F2-1F1F4",
+    "unicode_alternates": [],
+    "name": "macau",
+    "shortname": ":flag_mo:",
+    "category": "flags",
+    "aliases": [":mo:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "aomen", "mo"]
+  },
+  "flag_mr": {
+    "unicode": "1F1F2-1F1F7",
+    "unicode_alternates": [],
+    "name": "mauritania",
+    "shortname": ":flag_mr:",
+    "category": "flags",
+    "aliases": [":mr:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "muritaniyah", "mr"]
+  },
+  "flag_ms": {
+    "unicode": "1F1F2-1F1F8",
+    "unicode_alternates": [],
+    "name": "montserrat",
+    "shortname": ":flag_ms:",
+    "category": "flags",
+    "aliases": [":ms:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ms"]
+  },
+  "flag_mt": {
+    "unicode": "1F1F2-1F1F9",
+    "unicode_alternates": [],
+    "name": "malta",
+    "shortname": ":flag_mt:",
+    "category": "flags",
+    "aliases": [":mt:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "mt"]
+  },
+  "flag_mu": {
+    "unicode": "1F1F2-1F1FA",
+    "unicode_alternates": [],
+    "name": "mauritius",
+    "shortname": ":flag_mu:",
+    "category": "flags",
+    "aliases": [":mu:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "mu"]
+  },
+  "flag_mv": {
+    "unicode": "1F1F2-1F1FB",
+    "unicode_alternates": [],
+    "name": "maldives",
+    "shortname": ":flag_mv:",
+    "category": "flags",
+    "aliases": [":mv:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "dhivehi raajje", "mv"]
+  },
+  "flag_mw": {
+    "unicode": "1F1F2-1F1FC",
+    "unicode_alternates": [],
+    "name": "malawi",
+    "shortname": ":flag_mw:",
+    "category": "flags",
+    "aliases": [":mw:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "mw"]
+  },
+  "flag_mx": {
+    "unicode": "1F1F2-1F1FD",
+    "unicode_alternates": [],
+    "name": "mexico",
+    "shortname": ":flag_mx:",
+    "category": "flags",
+    "aliases": [":mx:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "mx"]
+  },
+  "flag_my": {
+    "unicode": "1F1F2-1F1FE",
+    "unicode_alternates": [],
+    "name": "malaysia",
+    "shortname": ":flag_my:",
+    "category": "flags",
+    "aliases": [":my:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "my"]
+  },
+  "flag_mz": {
+    "unicode": "1F1F2-1F1FF",
+    "unicode_alternates": [],
+    "name": "mozambique",
+    "shortname": ":flag_mz:",
+    "category": "flags",
+    "aliases": [":mz:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "mocambique", "mz"]
+  },
+  "flag_na": {
+    "unicode": "1F1F3-1F1E6",
+    "unicode_alternates": [],
+    "name": "namibia",
+    "shortname": ":flag_na:",
+    "category": "flags",
+    "aliases": [":na:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "na"]
+  },
+  "flag_nc": {
+    "unicode": "1F1F3-1F1E8",
+    "unicode_alternates": [],
+    "name": "new caledonia",
+    "shortname": ":flag_nc:",
+    "category": "flags",
+    "aliases": [":nc:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "nouvelle", "cal&eacute;donie", "caledonie", "nc"]
+  },
+  "flag_ne": {
+    "unicode": "1F1F3-1F1EA",
+    "unicode_alternates": [],
+    "name": "niger",
+    "shortname": ":flag_ne:",
+    "category": "flags",
+    "aliases": [":ne:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ne"]
+  },
+  "flag_ng": {
+    "unicode": "1F1F3-1F1EC",
+    "unicode_alternates": [],
+    "name": "nigeria",
+    "shortname": ":flag_ng:",
+    "category": "flags",
+    "aliases": [":nigeria:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ng"]
+  },
+  "flag_ni": {
+    "unicode": "1F1F3-1F1EE",
+    "unicode_alternates": [],
+    "name": "nicaragua",
+    "shortname": ":flag_ni:",
+    "category": "flags",
+    "aliases": [":ni:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ni"]
+  },
+  "flag_nl": {
+    "unicode": "1F1F3-1F1F1",
+    "unicode_alternates": [],
+    "name": "the netherlands",
+    "shortname": ":flag_nl:",
+    "category": "flags",
+    "aliases": [":nl:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "nederland", "holland", "nl"]
+  },
+  "flag_no": {
+    "unicode": "1F1F3-1F1F4",
+    "unicode_alternates": [],
+    "name": "norway",
+    "shortname": ":flag_no:",
+    "category": "flags",
+    "aliases": [":no:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "norge", "no"]
+  },
+  "flag_np": {
+    "unicode": "1F1F3-1F1F5",
+    "unicode_alternates": [],
+    "name": "nepal",
+    "shortname": ":flag_np:",
+    "category": "flags",
+    "aliases": [":np:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "np"]
+  },
+  "flag_nr": {
+    "unicode": "1F1F3-1F1F7",
+    "unicode_alternates": [],
+    "name": "nauru",
+    "shortname": ":flag_nr:",
+    "category": "flags",
+    "aliases": [":nr:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "nr"]
+  },
+  "flag_nu": {
+    "unicode": "1F1F3-1F1FA",
+    "unicode_alternates": [],
+    "name": "niue",
+    "shortname": ":flag_nu:",
+    "category": "flags",
+    "aliases": [":nu:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "nu"]
+  },
+  "flag_nz": {
+    "unicode": "1F1F3-1F1FF",
+    "unicode_alternates": [],
+    "name": "new zealand",
+    "shortname": ":flag_nz:",
+    "category": "flags",
+    "aliases": [":nz:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "aotearoa", "nz"]
+  },
+  "flag_om": {
+    "unicode": "1F1F4-1F1F2",
+    "unicode_alternates": [],
+    "name": "oman",
+    "shortname": ":flag_om:",
+    "category": "flags",
+    "aliases": [":om:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "saltanat uman", "om"]
+  },
+  "flag_pa": {
+    "unicode": "1F1F5-1F1E6",
+    "unicode_alternates": [],
+    "name": "panama",
+    "shortname": ":flag_pa:",
+    "category": "flags",
+    "aliases": [":pa:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "pa"]
+  },
+  "flag_pe": {
+    "unicode": "1F1F5-1F1EA",
+    "unicode_alternates": [],
+    "name": "peru",
+    "shortname": ":flag_pe:",
+    "category": "flags",
+    "aliases": [":pe:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "pe"]
+  },
+  "flag_pf": {
+    "unicode": "1F1F5-1F1EB",
+    "unicode_alternates": [],
+    "name": "french polynesia",
+    "shortname": ":flag_pf:",
+    "category": "flags",
+    "aliases": [":pf:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "polyn&eacute;sie fran&ccedil;aise", "polynesie francaise", "pf"]
+  },
+  "flag_pg": {
+    "unicode": "1F1F5-1F1EC",
+    "unicode_alternates": [],
+    "name": "papua new guinea",
+    "shortname": ":flag_pg:",
+    "category": "flags",
+    "aliases": [":pg:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "papua niu gini", "pg"]
+  },
+  "flag_ph": {
+    "unicode": "1F1F5-1F1ED",
+    "unicode_alternates": [],
+    "name": "the philippines",
+    "shortname": ":flag_ph:",
+    "category": "flags",
+    "aliases": [":ph:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "pilipinas", "ph"]
+  },
+  "flag_pk": {
+    "unicode": "1F1F5-1F1F0",
+    "unicode_alternates": [],
+    "name": "pakistan",
+    "shortname": ":flag_pk:",
+    "category": "flags",
+    "aliases": [":pk:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "pk"]
+  },
+  "flag_pl": {
+    "unicode": "1F1F5-1F1F1",
+    "unicode_alternates": [],
+    "name": "poland",
+    "shortname": ":flag_pl:",
+    "category": "flags",
+    "aliases": [":pl:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "polska", "pl"]
+  },
+  "flag_pr": {
+    "unicode": "1F1F5-1F1F7",
+    "unicode_alternates": [],
+    "name": "puerto rico",
+    "shortname": ":flag_pr:",
+    "category": "flags",
+    "aliases": [":pr:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "pr"]
+  },
+  "flag_ps": {
+    "unicode": "1F1F5-1F1F8",
+    "unicode_alternates": [],
+    "name": "palestinian authority",
+    "shortname": ":flag_ps:",
+    "category": "flags",
+    "aliases": [":ps:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ps"]
+  },
+  "flag_pt": {
+    "unicode": "1F1F5-1F1F9",
+    "unicode_alternates": [],
+    "name": "portugal",
+    "shortname": ":flag_pt:",
+    "category": "flags",
+    "aliases": [":pt:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "pt"]
+  },
+  "flag_pw": {
+    "unicode": "1F1F5-1F1FC",
+    "unicode_alternates": [],
+    "name": "palau",
+    "shortname": ":flag_pw:",
+    "category": "flags",
+    "aliases": [":pw:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "belau", "pw"]
+  },
+  "flag_py": {
+    "unicode": "1F1F5-1F1FE",
+    "unicode_alternates": [],
+    "name": "paraguay",
+    "shortname": ":flag_py:",
+    "category": "flags",
+    "aliases": [":py:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "py"]
+  },
+  "flag_qa": {
+    "unicode": "1F1F6-1F1E6",
+    "unicode_alternates": [],
+    "name": "qatar",
+    "shortname": ":flag_qa:",
+    "category": "flags",
+    "aliases": [":qa:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "dawlat qatar", "qa"]
+  },
+  "flag_ro": {
+    "unicode": "1F1F7-1F1F4",
+    "unicode_alternates": [],
+    "name": "romania",
+    "shortname": ":flag_ro:",
+    "category": "flags",
+    "aliases": [":ro:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ro"]
+  },
+  "flag_rs": {
+    "unicode": "1F1F7-1F1F8",
+    "unicode_alternates": [],
+    "name": "serbia",
+    "shortname": ":flag_rs:",
+    "category": "flags",
+    "aliases": [":rs:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "srbija", "rs"]
+  },
+  "flag_ru": {
+    "unicode": "1F1F7-1F1FA",
+    "unicode_alternates": [],
+    "name": "russia",
+    "shortname": ":flag_ru:",
+    "category": "flags",
+    "aliases": [":ru:"],
+    "aliases_ascii": [],
+    "keywords": ["nation", "russian", "country", "ru"]
+  },
+  "flag_rw": {
+    "unicode": "1F1F7-1F1FC",
+    "unicode_alternates": [],
+    "name": "rwanda",
+    "shortname": ":flag_rw:",
+    "category": "flags",
+    "aliases": [":rw:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "rw"]
+  },
+  "flag_sa": {
+    "unicode": "1F1F8-1F1E6",
+    "unicode_alternates": [],
+    "name": "saudi arabia",
+    "shortname": ":flag_sa:",
+    "category": "flags",
+    "aliases": [":saudiarabia:", ":saudi:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "al arabiyah as suudiyah", "sa"]
+  },
+  "flag_sb": {
+    "unicode": "1F1F8-1F1E7",
+    "unicode_alternates": [],
+    "name": "the solomon islands",
+    "shortname": ":flag_sb:",
+    "category": "flags",
+    "aliases": [":sb:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sb"]
+  },
+  "flag_sc": {
+    "unicode": "1F1F8-1F1E8",
+    "unicode_alternates": [],
+    "name": "the seychelles",
+    "shortname": ":flag_sc:",
+    "category": "flags",
+    "aliases": [":sc:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "seychelles", "sc"]
+  },
+  "flag_sd": {
+    "unicode": "1F1F8-1F1E9",
+    "unicode_alternates": [],
+    "name": "sudan",
+    "shortname": ":flag_sd:",
+    "category": "flags",
+    "aliases": [":sd:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "as-sudan", "sd"]
+  },
+  "flag_se": {
+    "unicode": "1F1F8-1F1EA",
+    "unicode_alternates": [],
+    "name": "sweden",
+    "shortname": ":flag_se:",
+    "category": "flags",
+    "aliases": [":se:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sverige", "se"]
+  },
+  "flag_sg": {
+    "unicode": "1F1F8-1F1EC",
+    "unicode_alternates": [],
+    "name": "singapore",
+    "shortname": ":flag_sg:",
+    "category": "flags",
+    "aliases": [":sg:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sg"]
+  },
+  "flag_sh": {
+    "unicode": "1F1F8-1F1ED",
+    "unicode_alternates": [],
+    "name": "saint helena",
+    "shortname": ":flag_sh:",
+    "category": "flags",
+    "aliases": [":sh:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sh"]
+  },
+  "flag_si": {
+    "unicode": "1F1F8-1F1EE",
+    "unicode_alternates": [],
+    "name": "slovenia",
+    "shortname": ":flag_si:",
+    "category": "flags",
+    "aliases": [":si:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "slovenija", "si"]
+  },
+  "flag_sk": {
+    "unicode": "1F1F8-1F1F0",
+    "unicode_alternates": [],
+    "name": "slovakia",
+    "shortname": ":flag_sk:",
+    "category": "flags",
+    "aliases": [":sk:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sk"]
+  },
+  "flag_sl": {
+    "unicode": "1F1F8-1F1F1",
+    "unicode_alternates": [],
+    "name": "sierra leone",
+    "shortname": ":flag_sl:",
+    "category": "flags",
+    "aliases": [":sl:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sl"]
+  },
+  "flag_sm": {
+    "unicode": "1F1F8-1F1F2",
+    "unicode_alternates": [],
+    "name": "san marino",
+    "shortname": ":flag_sm:",
+    "category": "flags",
+    "aliases": [":sm:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sm"]
+  },
+  "flag_sn": {
+    "unicode": "1F1F8-1F1F3",
+    "unicode_alternates": [],
+    "name": "senegal",
+    "shortname": ":flag_sn:",
+    "category": "flags",
+    "aliases": [":sn:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sn"]
+  },
+  "flag_so": {
+    "unicode": "1F1F8-1F1F4",
+    "unicode_alternates": [],
+    "name": "somalia",
+    "shortname": ":flag_so:",
+    "category": "flags",
+    "aliases": [":so:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "so"]
+  },
+  "flag_sr": {
+    "unicode": "1F1F8-1F1F7",
+    "unicode_alternates": [],
+    "name": "suriname",
+    "shortname": ":flag_sr:",
+    "category": "flags",
+    "aliases": [":sr:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sr"]
+  },
+  "flag_st": {
+    "unicode": "1F1F8-1F1F9",
+    "unicode_alternates": [],
+    "name": "sao tome and principe",
+    "shortname": ":flag_st:",
+    "category": "flags",
+    "aliases": [":st:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sao tome e principe", "st"]
+  },
+  "flag_sv": {
+    "unicode": "1F1F8-1F1FB",
+    "unicode_alternates": [],
+    "name": "el salvador",
+    "shortname": ":flag_sv:",
+    "category": "flags",
+    "aliases": [":sv:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sv"]
+  },
+  "flag_sy": {
+    "unicode": "1F1F8-1F1FE",
+    "unicode_alternates": [],
+    "name": "syria",
+    "shortname": ":flag_sy:",
+    "category": "flags",
+    "aliases": [":sy:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sy"]
+  },
+  "flag_sz": {
+    "unicode": "1F1F8-1F1FF",
+    "unicode_alternates": [],
+    "name": "swaziland",
+    "shortname": ":flag_sz:",
+    "category": "flags",
+    "aliases": [":sz:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "sz"]
+  },
+  "flag_td": {
+    "unicode": "1F1F9-1F1E9",
+    "unicode_alternates": [],
+    "name": "chad",
+    "shortname": ":flag_td:",
+    "category": "flags",
+    "aliases": [":td:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "tchad", "td"]
+  },
+  "flag_tg": {
+    "unicode": "1F1F9-1F1EC",
+    "unicode_alternates": [],
+    "name": "togo",
+    "shortname": ":flag_tg:",
+    "category": "flags",
+    "aliases": [":tg:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "republique togolaise", "tg"]
+  },
+  "flag_th": {
+    "unicode": "1F1F9-1F1ED",
+    "unicode_alternates": [],
+    "name": "thailand",
+    "shortname": ":flag_th:",
+    "category": "flags",
+    "aliases": [":th:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "prathet thai", "th"]
+  },
+  "flag_tj": {
+    "unicode": "1F1F9-1F1EF",
+    "unicode_alternates": [],
+    "name": "tajikistan",
+    "shortname": ":flag_tj:",
+    "category": "flags",
+    "aliases": [":tj:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "jumhurii tojikiston", "tj"]
+  },
+  "flag_tl": {
+    "unicode": "1F1F9-1F1F1",
+    "unicode_alternates": [],
+    "name": "east timor",
+    "shortname": ":flag_tl:",
+    "category": "flags",
+    "aliases": [":tl:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "tl"]
+  },
+  "flag_tm": {
+    "unicode": "1F1F9-1F1F2",
+    "unicode_alternates": [],
+    "name": "turkmenistan",
+    "shortname": ":flag_tm:",
+    "category": "flags",
+    "aliases": [":turkmenistan:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "tm"]
+  },
+  "flag_tn": {
+    "unicode": "1F1F9-1F1F3",
+    "unicode_alternates": [],
+    "name": "tunisia",
+    "shortname": ":flag_tn:",
+    "category": "flags",
+    "aliases": [":tn:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "tunis", "tn"]
+  },
+  "flag_to": {
+    "unicode": "1F1F9-1F1F4",
+    "unicode_alternates": [],
+    "name": "tonga",
+    "shortname": ":flag_to:",
+    "category": "flags",
+    "aliases": [":to:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "to"]
+  },
+  "flag_tr": {
+    "unicode": "1F1F9-1F1F7",
+    "unicode_alternates": [],
+    "name": "turkey",
+    "shortname": ":flag_tr:",
+    "category": "flags",
+    "aliases": [":tr:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "turkiye"]
+  },
+  "flag_tt": {
+    "unicode": "1F1F9-1F1F9",
+    "unicode_alternates": [],
+    "name": "trinidad and tobago",
+    "shortname": ":flag_tt:",
+    "category": "flags",
+    "aliases": [":tt:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "tt"]
+  },
+  "flag_tv": {
+    "unicode": "1F1F9-1F1FB",
+    "unicode_alternates": [],
+    "name": "tuvalu",
+    "shortname": ":flag_tv:",
+    "category": "flags",
+    "aliases": [":tuvalu:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "tv"]
+  },
+  "flag_tw": {
+    "unicode": "1F1F9-1F1FC",
+    "unicode_alternates": [],
+    "name": "the republic of china",
+    "shortname": ":flag_tw:",
+    "category": "flags",
+    "aliases": [":tw:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "taiwan", "tw"]
+  },
+  "flag_tz": {
+    "unicode": "1F1F9-1F1FF",
+    "unicode_alternates": [],
+    "name": "tanzania",
+    "shortname": ":flag_tz:",
+    "category": "flags",
+    "aliases": [":tz:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "tz"]
+  },
+  "flag_ua": {
+    "unicode": "1F1FA-1F1E6",
+    "unicode_alternates": [],
+    "name": "ukraine",
+    "shortname": ":flag_ua:",
+    "category": "flags",
+    "aliases": [":ua:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ukrayina", "ua"]
+  },
+  "flag_ug": {
+    "unicode": "1F1FA-1F1EC",
+    "unicode_alternates": [],
+    "name": "uganda",
+    "shortname": ":flag_ug:",
+    "category": "flags",
+    "aliases": [":ug:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ug"]
+  },
+  "flag_us": {
+    "unicode": "1F1FA-1F1F8",
+    "unicode_alternates": [],
+    "name": "united states",
+    "shortname": ":flag_us:",
+    "category": "flags",
+    "aliases": [":us:"],
+    "aliases_ascii": [],
+    "keywords": ["american", "country", "nation", "usa", "united states of america", "america", "old glory", "us"]
+  },
+  "flag_uy": {
+    "unicode": "1F1FA-1F1FE",
+    "unicode_alternates": [],
+    "name": "uruguay",
+    "shortname": ":flag_uy:",
+    "category": "flags",
+    "aliases": [":uy:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "uy"]
+  },
+  "flag_uz": {
+    "unicode": "1F1FA-1F1FF",
+    "unicode_alternates": [],
+    "name": "uzbekistan",
+    "shortname": ":flag_uz:",
+    "category": "flags",
+    "aliases": [":uz:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "uzbekiston respublikasi", "uz"]
+  },
+  "flag_va": {
+    "unicode": "1F1FB-1F1E6",
+    "unicode_alternates": [],
+    "name": "the vatican city",
+    "shortname": ":flag_va:",
+    "category": "flags",
+    "aliases": [":va:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "va"]
+  },
+  "flag_vc": {
+    "unicode": "1F1FB-1F1E8",
+    "unicode_alternates": [],
+    "name": "saint vincent and the grenadines",
+    "shortname": ":flag_vc:",
+    "category": "flags",
+    "aliases": [":vc:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "vc"]
+  },
+  "flag_ve": {
+    "unicode": "1F1FB-1F1EA",
+    "unicode_alternates": [],
+    "name": "venezuela",
+    "shortname": ":flag_ve:",
+    "category": "flags",
+    "aliases": [":ve:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "ve"]
+  },
+  "flag_vi": {
+    "unicode": "1F1FB-1F1EE",
+    "unicode_alternates": [],
+    "name": "u.s. virgin islands",
+    "shortname": ":flag_vi:",
+    "category": "flags",
+    "aliases": [":vi:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "vi"]
+  },
+  "flag_vn": {
+    "unicode": "1F1FB-1F1F3",
+    "unicode_alternates": [],
+    "name": "vietnam",
+    "shortname": ":flag_vn:",
+    "category": "flags",
+    "aliases": [":vn:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "viet nam", "vn"]
+  },
+  "flag_vu": {
+    "unicode": "1F1FB-1F1FA",
+    "unicode_alternates": [],
+    "name": "vanuatu",
+    "shortname": ":flag_vu:",
+    "category": "flags",
+    "aliases": [":vu:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "vu"]
+  },
+  "flag_wf": {
+    "unicode": "1F1FC-1F1EB",
+    "unicode_alternates": [],
+    "name": "wallis and futuna",
+    "shortname": ":flag_wf:",
+    "category": "flags",
+    "aliases": [":wf:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "wf"]
+  },
+  "flag_white": {
+    "unicode": "1F3F3",
+    "unicode_alternates": [],
+    "name": "waving white flag",
+    "shortname": ":flag_white:",
+    "category": "objects_symbols",
+    "aliases": [":waving_white_flag:"],
+    "aliases_ascii": [],
+    "keywords": ["symbol", "signal"]
+  },
+  "flag_ws": {
+    "unicode": "1F1FC-1F1F8",
+    "unicode_alternates": [],
+    "name": "samoa",
+    "shortname": ":flag_ws:",
+    "category": "flags",
+    "aliases": [":ws:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "american samoa", "ws"]
+  },
+  "flag_xk": {
+    "unicode": "1F1FD-1F1F0",
+    "unicode_alternates": [],
+    "name": "kosovo",
+    "shortname": ":flag_xk:",
+    "category": "flags",
+    "aliases": [":xk:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "xk"]
+  },
+  "flag_ye": {
+    "unicode": "1F1FE-1F1EA",
+    "unicode_alternates": [],
+    "name": "yemen",
+    "shortname": ":flag_ye:",
+    "category": "flags",
+    "aliases": [":ye:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "al yaman", "ye"]
+  },
+  "flag_za": {
+    "unicode": "1F1FF-1F1E6",
+    "unicode_alternates": [],
+    "name": "south africa",
+    "shortname": ":flag_za:",
+    "category": "flags",
+    "aliases": [":za:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation"]
+  },
+  "flag_zm": {
+    "unicode": "1F1FF-1F1F2",
+    "unicode_alternates": [],
+    "name": "zambia",
+    "shortname": ":flag_zm:",
+    "category": "flags",
+    "aliases": [":zm:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "zm"]
+  },
+  "flag_zw": {
+    "unicode": "1F1FF-1F1FC",
+    "unicode_alternates": [],
+    "name": "zimbabwe",
+    "shortname": ":flag_zw:",
+    "category": "flags",
+    "aliases": [":zw:"],
+    "aliases_ascii": [],
+    "keywords": ["country", "nation", "zw"]
+  },
+  "flags": {
+    "unicode": "1F38F",
+    "unicode_alternates": [],
+    "name": "carp streamer",
+    "shortname": ":flags:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["banner", "carp", "fish", "japanese", "koinobori", "children", "kids", "boys", "celebration", "happiness", "carp", "streamers", "japanese", "holiday", "flags"],
+    "moji": "🎏"
+  },
+  "flashlight": {
+    "unicode": "1F526",
+    "unicode_alternates": [],
+    "name": "electric torch",
+    "shortname": ":flashlight:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["dark"],
+    "moji": "🔦"
+  },
+  "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:"],
+    "aliases_ascii": [],
+    "keywords": ["oldschool", "save", "technology", "storage", "information", "computer", "drive", "megabyte"]
+  },
+  "floppy_disk": {
+    "unicode": "1F4BE",
+    "unicode_alternates": [],
+    "name": "floppy disk",
+    "shortname": ":floppy_disk:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["oldschool", "save", "technology", "floppy", "disk", "storage", "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"]
+  },
+  "flower_playing_cards": {
+    "unicode": "1F3B4",
+    "unicode_alternates": [],
+    "name": "flower playing cards",
+    "shortname": ":flower_playing_cards:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["playing", "card", "flower", "game", "august", "moon", "special"],
+    "moji": "🎴"
+  },
+  "flushed": {
+    "unicode": "1F633",
+    "unicode_alternates": [],
+    "name": "flushed face",
+    "shortname": ":flushed:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [":$", "=$"],
+    "keywords": ["blush", "face", "flattered", "flush", "blush", "red", "pink", "cheeks", "shy"],
+    "moji": "😳"
+  },
+  "fog": {
+    "unicode": "1F32B",
+    "unicode_alternates": [],
+    "name": "fog",
+    "shortname": ":fog:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["weather", "damp", "cloud", "hazy"]
+  },
+  "foggy": {
+    "unicode": "1F301",
+    "unicode_alternates": [],
+    "name": "foggy",
+    "shortname": ":foggy:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["mountain", "photo", "bridge", "weather", "fog", "foggy"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["NFL", "balls", "sports", "football", "ball", "sport", "america", "american"],
+    "moji": "🏈"
+  },
+  "footprints": {
+    "unicode": "1F463",
+    "unicode_alternates": [],
+    "name": "footprints",
+    "shortname": ":footprints:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["feet"],
+    "moji": "👣"
+  },
+  "fork_and_knife": {
+    "unicode": "1F374",
+    "unicode_alternates": [],
+    "name": "fork and knife",
+    "shortname": ":fork_and_knife:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cutlery", "kitchen", "fork", "knife", "restaurant", "meal", "food", "eat"],
+    "moji": "🍴"
+  },
+  "fork_knife_plate": {
+    "unicode": "1F37D",
+    "unicode_alternates": [],
+    "name": "fork and knife with plate",
+    "shortname": ":fork_knife_plate:",
+    "category": "travel_places",
+    "aliases": [":fork_and_knife_with_plate:"],
+    "aliases_ascii": [],
+    "keywords": ["meal", "food", "breakfast", "lunch", "dinner", "utensils", "setting"]
+  },
+  "fountain": {
+    "unicode": "26F2",
+    "unicode_alternates": ["26F2-FE0F"],
+    "name": "fountain",
+    "shortname": ":fountain:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["photo"],
+    "moji": "⛲"
+  },
+  "four": {
+    "moji": "4️⃣",
+    "unicode": "0034-20E3",
+    "unicode_alternates": ["0034-FE0F-20E3"],
+    "name": "digit four",
+    "shortname": ":four:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["4", "blue-square", "numbers"]
+  },
+  "four_leaf_clover": {
+    "unicode": "1F340",
+    "unicode_alternates": [],
+    "name": "four leaf clover",
+    "shortname": ":four_leaf_clover:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["lucky", "nature", "plant", "vegetable", "clover", "four", "leaf", "luck", "irish", "saint", "patrick", "green"],
+    "moji": "🍀"
+  },
+  "frame_photo": {
+    "unicode": "1F5BC",
+    "unicode_alternates": [],
+    "name": "frame with picture",
+    "shortname": ":frame_photo:",
+    "category": "objects_symbols",
+    "aliases": [":frame_with_picture:"],
+    "aliases_ascii": [],
+    "keywords": ["photo"]
+  },
+  "frame_tiles": {
+    "unicode": "1F5BD",
+    "unicode_alternates": [],
+    "name": "frame with tiles",
+    "shortname": ":frame_tiles:",
+    "category": "objects_symbols",
+    "aliases": [":frame_with_tiles:"],
+    "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:"],
+    "aliases_ascii": [],
+    "keywords": ["photo", "painting"]
+  },
+  "free": {
+    "unicode": "1F193",
+    "unicode_alternates": [],
+    "name": "squared free",
+    "shortname": ":free:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "words"],
+    "moji": "🆓"
+  },
+  "fried_shrimp": {
+    "unicode": "1F364",
+    "unicode_alternates": [],
+    "name": "fried shrimp",
+    "shortname": ":fried_shrimp:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "food", "shrimp", "fried", "seafood", "small", "fish"],
+    "moji": "🍤"
+  },
+  "fries": {
+    "unicode": "1F35F",
+    "unicode_alternates": [],
+    "name": "french fries",
+    "shortname": ":fries:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chips", "food", "fries", "french", "potato", "fry", "russet", "idaho"],
+    "moji": "🍟"
+  },
+  "frog": {
+    "unicode": "1F438",
+    "unicode_alternates": [],
+    "name": "frog face",
+    "shortname": ":frog:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature"],
+    "moji": "🐸"
+  },
+  "frowning": {
+    "unicode": "1F626",
+    "unicode_alternates": [],
+    "name": "frowning face with open mouth",
+    "shortname": ":frowning:",
+    "category": "emoticons",
+    "aliases": [":anguished:"],
+    "aliases_ascii": [],
+    "keywords": ["aw", "face", "frown", "sad", "pout", "sulk", "glower"],
+    "moji": "😦"
+  },
+  "fuelpump": {
+    "unicode": "26FD",
+    "unicode_alternates": ["26FD-FE0F"],
+    "name": "fuel pump",
+    "shortname": ":fuelpump:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["gas station", "petroleum"],
+    "moji": "⛽"
+  },
+  "full_moon": {
+    "unicode": "1F315",
+    "unicode_alternates": [],
+    "name": "full moon symbol",
+    "shortname": ":full_moon:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "yellow", "moon", "full", "sky", "night", "cheese", "phase", "monster", "spooky", "werewolves", "twilight"],
+    "moji": "🌕"
+  },
+  "full_moon_with_face": {
+    "unicode": "1F31D",
+    "unicode_alternates": [],
+    "name": "full moon with face",
+    "shortname": ":full_moon_with_face:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["night", "moon", "full", "anthropomorphic", "face", "sky", "night", "cheese", "phase", "spooky", "werewolves", "monsters"],
+    "moji": "🌝"
+  },
+  "game_die": {
+    "unicode": "1F3B2",
+    "unicode_alternates": [],
+    "name": "game die",
+    "shortname": ":game_die:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["dice", "game", "die", "dice", "craps", "gamble", "play"],
+    "moji": "🎲"
+  },
+  "gem": {
+    "unicode": "1F48E",
+    "unicode_alternates": [],
+    "name": "gem stone",
+    "shortname": ":gem:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue", "ruby"],
+    "moji": "💎"
+  },
+  "gemini": {
+    "unicode": "264A",
+    "unicode_alternates": ["264A-FE0F"],
+    "name": "gemini",
+    "shortname": ":gemini:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["gemini", "twins", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+    "moji": "♊"
+  },
+  "ghost": {
+    "unicode": "1F47B",
+    "unicode_alternates": [],
+    "name": "ghost",
+    "shortname": ":ghost:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["halloween"],
+    "moji": "👻"
+  },
+  "gift": {
+    "unicode": "1F381",
+    "unicode_alternates": [],
+    "name": "wrapped present",
+    "shortname": ":gift:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["birthday", "christmas", "present", "xmas", "gift", "present", "wrap", "package", "birthday", "wedding"],
+    "moji": "🎁"
+  },
+  "gift_heart": {
+    "unicode": "1F49D",
+    "unicode_alternates": [],
+    "name": "heart with ribbon",
+    "shortname": ":gift_heart:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["love", "valentines"],
+    "moji": "💝"
+  },
+  "girl": {
+    "unicode": "1F467",
+    "unicode_alternates": [],
+    "name": "girl",
+    "shortname": ":girl:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["female", "woman"],
+    "moji": "👧"
+  },
+  "girls_symbol": {
+    "unicode": "1F6CA",
+    "unicode_alternates": [],
+    "name": "girls symbol",
+    "shortname": ":girls_symbol:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["female", "child"]
+  },
+  "globe_with_meridians": {
+    "unicode": "1F310",
+    "unicode_alternates": [],
+    "name": "globe with meridians",
+    "shortname": ":globe_with_meridians:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["earth", "international", "world", "earth", "meridian", "globe", "space", "planet", "home"],
+    "moji": "🌐"
+  },
+  "goat": {
+    "unicode": "1F410",
+    "unicode_alternates": [],
+    "name": "goat",
+    "shortname": ":goat:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "goat", "sheep", "kid", "billy", "livestock"],
+    "moji": "🐐"
+  },
+  "golf": {
+    "unicode": "26F3",
+    "unicode_alternates": ["26F3-FE0F"],
+    "name": "flag in hole",
+    "shortname": ":golf:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["business", "sports"],
+    "moji": "⛳"
+  },
+  "golfer": {
+    "unicode": "1F3CC",
+    "unicode_alternates": [],
+    "name": "golfer",
+    "shortname": ":golfer:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sport", "par", "birdie", "eagle", "mulligan"]
+  },
+  "grapes": {
+    "unicode": "1F347",
+    "unicode_alternates": [],
+    "name": "grapes",
+    "shortname": ":grapes:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "fruit", "grapes", "wine", "vinegar", "fruit", "cluster", "vine"],
+    "moji": "🍇"
+  },
+  "green_apple": {
+    "unicode": "1F34F",
+    "unicode_alternates": [],
+    "name": "green apple",
+    "shortname": ":green_apple:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fruit", "nature", "apple", "fruit", "green", "pie", "granny", "smith", "core"],
+    "moji": "🍏"
+  },
+  "green_book": {
+    "unicode": "1F4D7",
+    "unicode_alternates": [],
+    "name": "green book",
+    "shortname": ":green_book:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["knowledge", "library", "read"],
+    "moji": "📗"
+  },
+  "green_heart": {
+    "unicode": "1F49A",
+    "unicode_alternates": [],
+    "name": "green heart",
+    "shortname": ":green_heart:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "like", "love", "valentines", "green", "heart", "love", "nature", "rebirth", "reborn", "jealous", "clingy", "envious", "possessive"],
+    "moji": "💚"
+  },
+  "grey_exclamation": {
+    "unicode": "2755",
+    "unicode_alternates": [],
+    "name": "white exclamation mark ornament",
+    "shortname": ":grey_exclamation:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["surprise"],
+    "moji": "❕"
+  },
+  "grey_question": {
+    "unicode": "2754",
+    "unicode_alternates": [],
+    "name": "white question mark ornament",
+    "shortname": ":grey_question:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["doubts"],
+    "moji": "❔"
+  },
+  "grimacing": {
+    "unicode": "1F62C",
+    "unicode_alternates": [],
+    "name": "grimacing face",
+    "shortname": ":grimacing:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "grimace", "teeth", "grimace", "disapprove", "pain"],
+    "moji": "😬"
+  },
+  "grin": {
+    "unicode": "1F601",
+    "unicode_alternates": [],
+    "name": "grinning face with smiling eyes",
+    "shortname": ":grin:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "happy", "joy", "smile", "grin", "grinning", "smiling", "smile", "smiley"],
+    "moji": "😁"
+  },
+  "grinning": {
+    "unicode": "1F600",
+    "unicode_alternates": [],
+    "name": "grinning face",
+    "shortname": ":grinning:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "happy", "joy", "smile", "grin", "grinning", "smiling", "smile", "smiley"],
+    "moji": "🕧"
+  },
+  "guardsman": {
+    "unicode": "1F482",
+    "unicode_alternates": [],
+    "name": "guardsman",
+    "shortname": ":guardsman:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["british", "gb", "male", "man", "uk", "guardsman", "guard", "bearskin", "hat", "british", "queen", "ceremonial", "military"],
+    "moji": "💂"
+  },
+  "guitar": {
+    "unicode": "1F3B8",
+    "unicode_alternates": [],
+    "name": "guitar",
+    "shortname": ":guitar:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["instrument", "music", "guitar", "string", "music", "instrument", "jam", "rock", "acoustic", "electric"],
+    "moji": "🎸"
+  },
+  "gun": {
+    "unicode": "1F52B",
+    "unicode_alternates": [],
+    "name": "pistol",
+    "shortname": ":gun:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["violence", "weapon"],
+    "moji": "🔫"
+  },
+  "haircut": {
+    "unicode": "1F487",
+    "unicode_alternates": [],
+    "name": "haircut",
+    "shortname": ":haircut:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["female", "girl", "woman"],
+    "moji": "💇"
+  },
+  "hamburger": {
+    "unicode": "1F354",
+    "unicode_alternates": [],
+    "name": "hamburger",
+    "shortname": ":hamburger:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "meat", "hamburger", "burger", "meat", "cow", "beef"],
+    "moji": "🍔"
+  },
+  "hammer": {
+    "unicode": "1F528",
+    "unicode_alternates": [],
+    "name": "hammer",
+    "shortname": ":hammer:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["done", "judge", "law", "ruling", "tools", "verdict"],
+    "moji": "🔨"
+  },
+  "hamster": {
+    "unicode": "1F439",
+    "unicode_alternates": [],
+    "name": "hamster face",
+    "shortname": ":hamster:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature"],
+    "moji": "🐹"
+  },
+  "hand_splayed": {
+    "unicode": "1F590",
+    "unicode_alternates": [],
+    "name": "raised hand with fingers splayed",
+    "shortname": ":hand_splayed:",
+    "category": "people",
+    "aliases": [":raised_hand_with_fingers_splayed:"],
+    "aliases_ascii": [],
+    "keywords": ["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:"],
+    "aliases_ascii": [],
+    "keywords": ["hi", "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"]
+  },
+  "handbag": {
+    "unicode": "1F45C",
+    "unicode_alternates": [],
+    "name": "handbag",
+    "shortname": ":handbag:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["accessories", "accessory", "bag", "fashion"],
+    "moji": "👜"
+  },
+  "hard_disk": {
+    "unicode": "1F5B4",
+    "unicode_alternates": [],
+    "name": "hard disk",
+    "shortname": ":hard_disk:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["save", "technology", "storage", "information", "computer", "drive", "megabyte", "gigabyte", "hd"]
+  },
+  "hash": {
+    "moji": "#⃣",
+    "unicode": "0023-20E3",
+    "unicode_alternates": ["0023-FE0F-20E3"],
+    "name": "number sign",
+    "shortname": ":hash:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["symbol"]
+  },
+  "hatched_chick": {
+    "unicode": "1F425",
+    "unicode_alternates": [],
+    "name": "front-facing baby chick",
+    "shortname": ":hatched_chick:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["baby", "chicken", "chick", "baby", "bird", "chicken", "young", "woman", "cute"],
+    "moji": "🐥"
+  },
+  "hatching_chick": {
+    "unicode": "1F423",
+    "unicode_alternates": [],
+    "name": "hatching chick",
+    "shortname": ":hatching_chick:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["born", "chicken", "egg", "chick", "egg", "baby", "bird", "chicken", "young", "woman", "cute"],
+    "moji": "🐣"
+  },
+  "headphones": {
+    "unicode": "1F3A7",
+    "unicode_alternates": [],
+    "name": "headphone",
+    "shortname": ":headphones:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["gadgets", "music", "score", "headphone", "sound", "music", "ears", "beats", "buds", "audio", "listen"],
+    "moji": "🎧"
+  },
+  "hear_no_evil": {
+    "unicode": "1F649",
+    "unicode_alternates": [],
+    "name": "hear-no-evil monkey",
+    "shortname": ":hear_no_evil:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "monkey", "monkey", "ears", "hear", "sound", "kikazaru"],
+    "moji": "🙉"
+  },
+  "heart": {
+    "moji": "❤",
+    "unicode": "2764",
+    "unicode_alternates": ["2764-FE0F"],
+    "name": "heavy black heart",
+    "shortname": ":heart:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": ["<3"],
+    "keywords": ["like", "love", "red", "pink", "black", "heart", "love", "passion", "romance", "intense", "desire", "death", "evil", "cold", "valentines"]
+  },
+  "heart_decoration": {
+    "unicode": "1F49F",
+    "unicode_alternates": [],
+    "name": "heart decoration",
+    "shortname": ":heart_decoration:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["like", "love", "purple-square"],
+    "moji": "💟"
+  },
+  "heart_eyes": {
+    "unicode": "1F60D",
+    "unicode_alternates": [],
+    "name": "smiling face with heart-shaped eyes",
+    "shortname": ":heart_eyes:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "crush", "face", "infatuation", "like", "love", "valentines", "smiling", "heart", "lovestruck", "love", "flirt", "smile", "heart-shaped"],
+    "moji": "😍"
+  },
+  "heart_eyes_cat": {
+    "unicode": "1F63B",
+    "unicode_alternates": [],
+    "name": "smiling cat face with heart-shaped eyes",
+    "shortname": ":heart_eyes_cat:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "animal", "cats", "like", "love", "valentines", "lovestruck", "love", "heart"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "like", "love", "valentines"],
+    "moji": "💓"
+  },
+  "heartpulse": {
+    "unicode": "1F497",
+    "unicode_alternates": [],
+    "name": "growing heart",
+    "shortname": ":heartpulse:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "like", "love", "valentines"],
+    "moji": "💗"
+  },
+  "hearts": {
+    "unicode": "2665",
+    "unicode_alternates": ["2665-FE0F"],
+    "name": "black heart suit",
+    "shortname": ":hearts:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cards", "poker"],
+    "moji": "♥"
+  },
+  "heavy_check_mark": {
+    "unicode": "2714",
+    "unicode_alternates": ["2714-FE0F"],
+    "name": "heavy check mark",
+    "shortname": ":heavy_check_mark:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nike", "ok"],
+    "moji": "✔"
+  },
+  "heavy_division_sign": {
+    "unicode": "2797",
+    "unicode_alternates": [],
+    "name": "heavy division sign",
+    "shortname": ":heavy_division_sign:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["calculation", "divide", "math"],
+    "moji": "➗"
+  },
+  "heavy_dollar_sign": {
+    "unicode": "1F4B2",
+    "unicode_alternates": [],
+    "name": "heavy dollar sign",
+    "shortname": ":heavy_dollar_sign:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["currency", "money", "payment", "dollar", "currency", "money", "cash", "sale", "purchase", "value"],
+    "moji": "💲"
+  },
+  "heavy_minus_sign": {
+    "unicode": "2796",
+    "unicode_alternates": [],
+    "name": "heavy minus sign",
+    "shortname": ":heavy_minus_sign:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["calculation", "math"],
+    "moji": "➖"
+  },
+  "heavy_multiplication_x": {
+    "unicode": "2716",
+    "unicode_alternates": ["2716-FE0F"],
+    "name": "heavy multiplication x",
+    "shortname": ":heavy_multiplication_x:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["calculation", "math"],
+    "moji": "✖"
+  },
+  "heavy_plus_sign": {
+    "unicode": "2795",
+    "unicode_alternates": [],
+    "name": "heavy plus sign",
+    "shortname": ":heavy_plus_sign:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["calculation", "math"],
+    "moji": "➕"
+  },
+  "helicopter": {
+    "unicode": "1F681",
+    "unicode_alternates": [],
+    "name": "helicopter",
+    "shortname": ":helicopter:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle", "helicopter", "helo", "gyro", "gyrocopter"],
+    "moji": "🚁"
+  },
+  "herb": {
+    "unicode": "1F33F",
+    "unicode_alternates": [],
+    "name": "herb",
+    "shortname": ":herb:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["grass", "lawn", "medicine", "plant", "vegetable", "weed", "herb", "spice", "plant", "cook", "cooking"],
+    "moji": "🌿"
+  },
+  "hibiscus": {
+    "unicode": "1F33A",
+    "unicode_alternates": [],
+    "name": "hibiscus",
+    "shortname": ":hibiscus:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["flowers", "plant", "vegetable", "hibiscus", "flower", "warm"],
+    "moji": "🌺"
+  },
+  "high_brightness": {
+    "unicode": "1F506",
+    "unicode_alternates": [],
+    "name": "high brightness symbol",
+    "shortname": ":high_brightness:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["light", "summer", "sun"],
+    "moji": "🔆"
+  },
+  "high_heel": {
+    "unicode": "1F460",
+    "unicode_alternates": [],
+    "name": "high-heeled shoe",
+    "shortname": ":high_heel:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fashion", "female", "shoes"],
+    "moji": "👠"
+  },
+  "hole": {
+    "unicode": "1F573",
+    "unicode_alternates": [],
+    "name": "hole",
+    "shortname": ":hole:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["pit", "well"]
+  },
+  "homes": {
+    "unicode": "1F3D8",
+    "unicode_alternates": [],
+    "name": "house buildings",
+    "shortname": ":homes:",
+    "category": "travel_places",
+    "aliases": [":house_buildings:"],
+    "aliases_ascii": [],
+    "keywords": ["home", "residence", "dwelling", "mansion", "bungalow", "ranch", "craftsman"]
+  },
+  "honey_pot": {
+    "unicode": "1F36F",
+    "unicode_alternates": [],
+    "name": "honey pot",
+    "shortname": ":honey_pot:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bees", "sweet", "honey", "pot", "bees", "pooh", "bear"],
+    "moji": "🍯"
+  },
+  "horse": {
+    "unicode": "1F434",
+    "unicode_alternates": [],
+    "name": "horse face",
+    "shortname": ":horse:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "brown"],
+    "moji": "🐴"
+  },
+  "horse_racing": {
+    "unicode": "1F3C7",
+    "unicode_alternates": [],
+    "name": "horse racing",
+    "shortname": ":horse_racing:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "betting", "competition", "horse", "race", "racing", "jockey", "triple crown"],
+    "moji": "🏇"
+  },
+  "hospital": {
+    "unicode": "1F3E5",
+    "unicode_alternates": [],
+    "name": "hospital",
+    "shortname": ":hospital:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building", "doctor", "health", "surgery"],
+    "moji": "🏥"
+  },
+  "hot_pepper": {
+    "unicode": "1F336",
+    "unicode_alternates": [],
+    "name": "hot pepper",
+    "shortname": ":hot_pepper:",
+    "category": "food_drink",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "nature", "spicy", "chili", "cayenne", "habanero", "jalapeno"]
+  },
+  "hotel": {
+    "unicode": "1F3E8",
+    "unicode_alternates": [],
+    "name": "hotel",
+    "shortname": ":hotel:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["accomodation", "building", "checkin", "whotel", "hotel", "motel", "holiday inn", "hospital"],
+    "moji": "🏨"
+  },
+  "hotsprings": {
+    "unicode": "2668",
+    "unicode_alternates": ["2668-FE0F"],
+    "name": "hot springs",
+    "shortname": ":hotsprings:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bath", "relax", "warm"],
+    "moji": "♨"
+  },
+  "hourglass": {
+    "unicode": "231B",
+    "unicode_alternates": ["231B-FE0F"],
+    "name": "hourglass",
+    "shortname": ":hourglass:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clock", "oldschool", "time"],
+    "moji": "⌛"
+  },
+  "hourglass_flowing_sand": {
+    "unicode": "23F3",
+    "unicode_alternates": [],
+    "name": "hourglass with flowing sand",
+    "shortname": ":hourglass_flowing_sand:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["countdown", "oldschool", "time"],
+    "moji": "⏳"
+  },
+  "house": {
+    "unicode": "1F3E0",
+    "unicode_alternates": [],
+    "name": "house building",
+    "shortname": ":house:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building", "home", "house", "home", "residence", "dwelling", "mansion", "bungalow", "ranch", "craftsman"],
+    "moji": "🏠"
+  },
+  "house_abandoned": {
+    "unicode": "1F3DA",
+    "unicode_alternates": [],
+    "name": "derelict house building",
+    "shortname": ":house_abandoned:",
+    "category": "travel_places",
+    "aliases": [":derelict_house_building:"],
+    "aliases_ascii": [],
+    "keywords": ["home", "residence", "dwelling", "mansion", "bungalow", "ranch", "craftsman", "boarded", "abandoned", "vacant", "run down", "shoddy"]
+  },
+  "house_with_garden": {
+    "unicode": "1F3E1",
+    "unicode_alternates": [],
+    "name": "house with garden",
+    "shortname": ":house_with_garden:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["home", "nature", "plant"],
+    "moji": "🏡"
+  },
+  "hushed": {
+    "unicode": "1F62F",
+    "unicode_alternates": [],
+    "name": "hushed face",
+    "shortname": ":hushed:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "woo", "quiet", "hush", "whisper", "silent"],
+    "moji": "😯"
+  },
+  "ice_cream": {
+    "unicode": "1F368",
+    "unicode_alternates": [],
+    "name": "ice cream",
+    "shortname": ":ice_cream:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["desert", "food", "hot", "icecream", "ice", "cream", "dairy", "dessert", "cold", "soft", "serve", "cone", "waffle"],
+    "moji": "🍨"
+  },
+  "icecream": {
+    "unicode": "1F366",
+    "unicode_alternates": [],
+    "name": "soft ice cream",
+    "shortname": ":icecream:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["desert", "food", "hot", "icecream", "ice", "cream", "dairy", "dessert", "cold", "soft", "serve", "cone", "yogurt"],
+    "moji": "🍦"
+  },
+  "ideograph_advantage": {
+    "unicode": "1F250",
+    "unicode_alternates": [],
+    "name": "circled ideograph advantage",
+    "shortname": ":ideograph_advantage:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "get", "kanji", "obtain"],
+    "moji": "🉐"
+  },
+  "imp": {
+    "unicode": "1F47F",
+    "unicode_alternates": [],
+    "name": "imp",
+    "shortname": ":imp:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["angry", "devil", "evil", "horns", "cute", "devil"],
+    "moji": "👿"
+  },
+  "inbox_tray": {
+    "unicode": "1F4E5",
+    "unicode_alternates": [],
+    "name": "inbox tray",
+    "shortname": ":inbox_tray:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["documents", "email"],
+    "moji": "📥"
+  },
+  "incoming_envelope": {
+    "unicode": "1F4E8",
+    "unicode_alternates": [],
+    "name": "incoming envelope",
+    "shortname": ":incoming_envelope:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["email", "inbox"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["female", "girl", "human", "woman", "information", "help", "question", "answer", "sassy", "unimpressed", "attitude", "snarky"],
+    "moji": "💁"
+  },
+  "information_source": {
+    "unicode": "2139",
+    "unicode_alternates": ["2139-FE0F"],
+    "name": "information source",
+    "shortname": ":information_source:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alphabet", "blue-square", "letter"],
+    "moji": "ℹ"
+  },
+  "innocent": {
+    "unicode": "1F607",
+    "unicode_alternates": [],
+    "name": "smiling face with halo",
+    "shortname": ":innocent:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": ["O:-)", "0:-3", "0:3", "0:-)", "0:)", "0;^)", "O:-)", "O:)", "O;-)", "O=)", "0;-)", "O:-3", "O:3"],
+    "keywords": ["angel", "face", "halo", "halo", "angel", "innocent", "ring", "circle", "heaven"],
+    "moji": "😇"
+  },
+  "interrobang": {
+    "unicode": "2049",
+    "unicode_alternates": ["2049-FE0F"],
+    "name": "exclamation question mark",
+    "shortname": ":interrobang:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["punctuation", "surprise", "wat"],
+    "moji": "⁉"
+  },
+  "iphone": {
+    "unicode": "1F4F1",
+    "unicode_alternates": [],
+    "name": "mobile phone",
+    "shortname": ":iphone:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["apple", "dial", "gadgets", "technology"],
+    "moji": "📱"
+  },
+  "island": {
+    "unicode": "1F3DD",
+    "unicode_alternates": [],
+    "name": "desert island",
+    "shortname": ":island:",
+    "category": "travel_places",
+    "aliases": [":desert_island:"],
+    "aliases_ascii": [],
+    "keywords": ["land", "solitude", "alone"]
+  },
+  "izakaya_lantern": {
+    "unicode": "1F3EE",
+    "unicode_alternates": [],
+    "name": "izakaya lantern",
+    "shortname": ":izakaya_lantern:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["light", "izakaya", "lantern", "stay", "drink", "alcohol", "bar", "sake", "restaurant"],
+    "moji": "🏮"
+  },
+  "jack_o_lantern": {
+    "unicode": "1F383",
+    "unicode_alternates": [],
+    "name": "jack-o-lantern",
+    "shortname": ":jack_o_lantern:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["halloween", "jack-o-lantern", "pumpkin", "halloween", "holiday", "carve", "autumn", "fall", "october", "saints", "costume", "spooky", "horror", "scary", "scared", "dead"],
+    "moji": "🎃"
+  },
+  "japan": {
+    "unicode": "1F5FE",
+    "unicode_alternates": [],
+    "name": "silhouette of japan",
+    "shortname": ":japan:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nation"],
+    "moji": "🗾"
+  },
+  "japanese_castle": {
+    "unicode": "1F3EF",
+    "unicode_alternates": [],
+    "name": "japanese castle",
+    "shortname": ":japanese_castle:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building", "photo", "castle", "japanese", "residence", "royalty", "fort", "fortified", "fortress"],
+    "moji": "🏯"
+  },
+  "japanese_goblin": {
+    "unicode": "1F47A",
+    "unicode_alternates": [],
+    "name": "japanese goblin",
+    "shortname": ":japanese_goblin:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["evil", "mask", "red", "japanese", "tengu", "supernatural", "avian", "demon", "goblin", "mask", "theater", "nose", "frown", "mustache", "anger", "frustration"],
+    "moji": "👺"
+  },
+  "japanese_ogre": {
+    "unicode": "1F479",
+    "unicode_alternates": [],
+    "name": "japanese ogre",
+    "shortname": ":japanese_ogre:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["monster", "japanese", "oni", "demon", "troll", "ogre", "folklore", "monster", "devil", "mask", "theater", "horns", "teeth"],
+    "moji": "👹"
+  },
+  "jeans": {
+    "unicode": "1F456",
+    "unicode_alternates": [],
+    "name": "jeans",
+    "shortname": ":jeans:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fashion", "shopping", "jeans", "pants", "blue", "denim", "levi&#039;s", "levi", "designer", "work", "skinny"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [":')", ":'-)"],
+    "keywords": ["cry", "face", "haha", "happy", "tears", "tears", "cry", "joy", "happy", "weep"],
+    "moji": "😂"
+  },
+  "joy_cat": {
+    "unicode": "1F639",
+    "unicode_alternates": [],
+    "name": "cat face with tears of joy",
+    "shortname": ":joy_cat:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "cats", "haha", "happy", "tears", "happy", "tears", "cry", "joy"],
+    "moji": "😹"
+  },
+  "joystick": {
+    "unicode": "1F579",
+    "unicode_alternates": [],
+    "name": "joystick",
+    "shortname": ":joystick:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["games", "atari", "controller"]
+  },
+  "key": {
+    "unicode": "1F511",
+    "unicode_alternates": [],
+    "name": "key",
+    "shortname": ":key:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["door", "lock", "password"],
+    "moji": "🔑"
+  },
+  "key2": {
+    "unicode": "1F5DD",
+    "unicode_alternates": [],
+    "name": "old key",
+    "shortname": ":key2:",
+    "category": "objects_symbols",
+    "aliases": [":old_key:"],
+    "aliases_ascii": [],
+    "keywords": ["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:"],
+    "aliases_ascii": [],
+    "keywords": ["computer", "input", "desktop"]
+  },
+  "keyboard_with_jacks": {
+    "unicode": "1F398",
+    "unicode_alternates": [],
+    "name": "musical keyboard with jacks",
+    "shortname": ":keyboard_with_jacks:",
+    "category": "objects_symbols",
+    "aliases": [":musical_keyboard_with_jacks:"],
+    "aliases_ascii": [],
+    "keywords": ["music", "instrument", "midi"]
+  },
+  "keycap_ten": {
+    "unicode": "1F51F",
+    "unicode_alternates": [],
+    "name": "keycap ten",
+    "shortname": ":keycap_ten:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["10", "blue-square", "numbers"],
+    "moji": "🔟"
+  },
+  "kimono": {
+    "unicode": "1F458",
+    "unicode_alternates": [],
+    "name": "kimono",
+    "shortname": ":kimono:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["dress", "fashion", "female", "japanese", "women"],
+    "moji": "👘"
+  },
+  "kiss": {
+    "unicode": "1F48B",
+    "unicode_alternates": [],
+    "name": "kiss mark",
+    "shortname": ":kiss:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "face", "like", "lips", "love", "valentines"],
+    "moji": "💋"
+  },
+  "kiss_mm": {
+    "unicode": "1F468-2764-1F48B-1F468",
+    "unicode_alternates": ["1F468-200D-2764-FE0F-200D-1F48B-200D-1F468"],
+    "name": "kiss (man,man)",
+    "shortname": ":kiss_mm:",
+    "category": "people",
+    "aliases": [":couplekiss_mm:"],
+    "aliases_ascii": [],
+    "keywords": ["dating", "like", "love", "marriage", "valentines", "couple"]
+  },
+  "kiss_ww": {
+    "unicode": "1F469-2764-1F48B-1F469",
+    "unicode_alternates": ["1F469-200D-2764-FE0F-200D-1F48B-200D-1F469"],
+    "name": "kiss (woman,woman)",
+    "shortname": ":kiss_ww:",
+    "category": "people",
+    "aliases": [":couplekiss_ww:"],
+    "aliases_ascii": [],
+    "keywords": ["dating", "like", "love", "marriage", "valentines", "couple"]
+  },
+  "kissing": {
+    "unicode": "1F617",
+    "unicode_alternates": [],
+    "name": "kissing face",
+    "shortname": ":kissing:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["3", "face", "infatuation", "like", "love", "valentines", "kissing", "kiss", "pucker", "lips", "smooch"],
+    "moji": "😗"
+  },
+  "kissing_cat": {
+    "unicode": "1F63D",
+    "unicode_alternates": [],
+    "name": "kissing cat face with closed eyes",
+    "shortname": ":kissing_cat:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "cats", "passion", "kiss", "puckered", "heart", "love"],
+    "moji": "😽"
+  },
+  "kissing_closed_eyes": {
+    "unicode": "1F61A",
+    "unicode_alternates": [],
+    "name": "kissing face with closed eyes",
+    "shortname": ":kissing_closed_eyes:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "face", "infatuation", "like", "love", "valentines", "kissing", "kiss", "passion", "puckered", "heart", "love", "smooch"],
+    "moji": "😚"
+  },
+  "kissing_heart": {
+    "unicode": "1F618",
+    "unicode_alternates": [],
+    "name": "face throwing a kiss",
+    "shortname": ":kissing_heart:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [":*", ":-*", "=*", ":^*"],
+    "keywords": ["affection", "face", "infatuation", "kiss", "blowing kiss", "heart", "love", "lips", "like", "love", "valentines"],
+    "moji": "😘"
+  },
+  "kissing_smiling_eyes": {
+    "unicode": "1F619",
+    "unicode_alternates": [],
+    "name": "kissing face with smiling eyes",
+    "shortname": ":kissing_smiling_eyes:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "face", "infatuation", "valentines", "kissing", "kiss", "smile", "pucker", "lips", "smooch"],
+    "moji": "😙"
+  },
+  "knife": {
+    "unicode": "1F52A",
+    "unicode_alternates": [],
+    "name": "hocho",
+    "shortname": ":knife:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🔪"
+  },
+  "koala": {
+    "unicode": "1F428",
+    "unicode_alternates": [],
+    "name": "koala",
+    "shortname": ":koala:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature"],
+    "moji": "🐨"
+  },
+  "koko": {
+    "unicode": "1F201",
+    "unicode_alternates": [],
+    "name": "squared katakana koko",
+    "shortname": ":koko:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "destination", "here", "japanese", "katakana"],
+    "moji": "🈁"
+  },
+  "label": {
+    "unicode": "1F3F7",
+    "unicode_alternates": [],
+    "name": "label",
+    "shortname": ":label:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["tag"]
+  },
+  "large_blue_circle": {
+    "unicode": "1F535",
+    "unicode_alternates": [],
+    "name": "large blue circle",
+    "shortname": ":large_blue_circle:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🔵"
+  },
+  "large_blue_diamond": {
+    "unicode": "1F537",
+    "unicode_alternates": [],
+    "name": "large blue diamond",
+    "shortname": ":large_blue_diamond:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "🔷"
+  },
+  "large_orange_diamond": {
+    "unicode": "1F536",
+    "unicode_alternates": [],
+    "name": "large orange diamond",
+    "shortname": ":large_orange_diamond:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "🔶"
+  },
+  "last_quarter_moon": {
+    "unicode": "1F317",
+    "unicode_alternates": [],
+    "name": "last quarter moon symbol",
+    "shortname": ":last_quarter_moon:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "moon", "last", "quarter", "sky", "night", "cheese", "phase"],
+    "moji": "🌗"
+  },
+  "last_quarter_moon_with_face": {
+    "unicode": "1F31C",
+    "unicode_alternates": [],
+    "name": "last quarter moon with face",
+    "shortname": ":last_quarter_moon_with_face:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "moon", "last", "quarter", "anthropomorphic", "face", "sky", "night", "cheese", "phase"],
+    "moji": "🌜"
+  },
+  "laughing": {
+    "unicode": "1F606",
+    "unicode_alternates": [],
+    "name": "smiling face with open mouth and tightly-closed ey",
+    "shortname": ":laughing:",
+    "category": "emoticons",
+    "aliases": [":satisfied:"],
+    "aliases_ascii": [">:)", ">;)", ">:-)", ">=)"],
+    "keywords": ["happy", "joy", "lol", "smiling", "laughing", "laugh"],
+    "moji": "😆"
+  },
+  "leaves": {
+    "unicode": "1F343",
+    "unicode_alternates": [],
+    "name": "leaf fluttering in wind",
+    "shortname": ":leaves:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["grass", "lawn", "nature", "plant", "tree", "vegetable", "leaves", "leaf", "wind", "float", "fluttering"],
+    "moji": "🍃"
+  },
+  "ledger": {
+    "unicode": "1F4D2",
+    "unicode_alternates": [],
+    "name": "ledger",
+    "shortname": ":ledger:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["notes", "paper"],
+    "moji": "📒"
+  },
+  "left_luggage": {
+    "unicode": "1F6C5",
+    "unicode_alternates": [],
+    "name": "left luggage",
+    "shortname": ":left_luggage:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "travel", "bag", "baggage", "luggage", "travel"],
+    "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": ["2194-FE0F"],
+    "name": "left right arrow",
+    "shortname": ":left_right_arrow:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "↔"
+  },
+  "leftwards_arrow_with_hook": {
+    "unicode": "21A9",
+    "unicode_alternates": ["21A9-FE0F"],
+    "name": "leftwards arrow with hook",
+    "shortname": ":leftwards_arrow_with_hook:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "↩"
+  },
+  "lemon": {
+    "unicode": "1F34B",
+    "unicode_alternates": [],
+    "name": "lemon",
+    "shortname": ":lemon:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fruit", "nature", "lemon", "yellow", "citrus"],
+    "moji": "🍋"
+  },
+  "leo": {
+    "unicode": "264C",
+    "unicode_alternates": ["264C-FE0F"],
+    "name": "leo",
+    "shortname": ":leo:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["leo", "lion", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+    "moji": "♌"
+  },
+  "leopard": {
+    "unicode": "1F406",
+    "unicode_alternates": [],
+    "name": "leopard",
+    "shortname": ":leopard:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "leopard", "cat", "spot", "spotted", "sexy"],
+    "moji": "🐆"
+  },
+  "level_slider": {
+    "unicode": "1F39A",
+    "unicode_alternates": [],
+    "name": "level slider",
+    "shortname": ":level_slider:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["controls"]
+  },
+  "levitate": {
+    "unicode": "1F574",
+    "unicode_alternates": [],
+    "name": "man in business suit levitating",
+    "shortname": ":levitate:",
+    "category": "people",
+    "aliases": [":man_in_business_suit_levitating:"],
+    "aliases_ascii": [],
+    "keywords": ["hover", "exclamation"]
+  },
+  "libra": {
+    "unicode": "264E",
+    "unicode_alternates": ["264E-FE0F"],
+    "name": "libra",
+    "shortname": ":libra:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["libra", "scales", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+    "moji": "♎"
+  },
+  "lifter": {
+    "unicode": "1F3CB",
+    "unicode_alternates": [],
+    "name": "weight lifter",
+    "shortname": ":lifter:",
+    "category": "activity",
+    "aliases": [":weight_lifter:"],
+    "aliases_ascii": [],
+    "keywords": ["bench", "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"]
+  },
+  "light_rail": {
+    "unicode": "1F688",
+    "unicode_alternates": [],
+    "name": "light rail",
+    "shortname": ":light_rail:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle", "train", "rail", "light"],
+    "moji": "🚈"
+  },
+  "link": {
+    "unicode": "1F517",
+    "unicode_alternates": [],
+    "name": "link symbol",
+    "shortname": ":link:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["rings", "url"],
+    "moji": "🔗"
+  },
+  "lips": {
+    "unicode": "1F444",
+    "unicode_alternates": [],
+    "name": "mouth",
+    "shortname": ":lips:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["kiss", "mouth"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fashion", "female", "girl"],
+    "moji": "💄"
+  },
+  "lock": {
+    "unicode": "1F512",
+    "unicode_alternates": [],
+    "name": "lock",
+    "shortname": ":lock:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["password", "security"],
+    "moji": "🔒"
+  },
+  "lock_with_ink_pen": {
+    "unicode": "1F50F",
+    "unicode_alternates": [],
+    "name": "lock with ink pen",
+    "shortname": ":lock_with_ink_pen:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["secret", "security"],
+    "moji": "🔏"
+  },
+  "lollipop": {
+    "unicode": "1F36D",
+    "unicode_alternates": [],
+    "name": "lollipop",
+    "shortname": ":lollipop:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["candy", "food", "snack", "sweet", "lollipop", "stick", "lick", "sweet", "sugar", "candy"],
+    "moji": "🍭"
+  },
+  "loop": {
+    "unicode": "27BF",
+    "unicode_alternates": [],
+    "name": "double curly loop",
+    "shortname": ":loop:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["curly"],
+    "moji": "➿"
+  },
+  "loud_sound": {
+    "unicode": "1F50A",
+    "unicode_alternates": [],
+    "name": "speaker with three sound waves",
+    "shortname": ":loud_sound:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🔊"
+  },
+  "loudspeaker": {
+    "unicode": "1F4E2",
+    "unicode_alternates": [],
+    "name": "public address loudspeaker",
+    "shortname": ":loudspeaker:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sound", "volume"],
+    "moji": "📢"
+  },
+  "love_hotel": {
+    "unicode": "1F3E9",
+    "unicode_alternates": [],
+    "name": "love hotel",
+    "shortname": ":love_hotel:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "dating", "like", "love", "hotel", "love", "sex", "romance", "leisure", "adultery", "prostitution", "hospital", "birth", "happy"],
+    "moji": "🏩"
+  },
+  "love_letter": {
+    "unicode": "1F48C",
+    "unicode_alternates": [],
+    "name": "love letter",
+    "shortname": ":love_letter:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "email", "envelope", "like", "valentines", "love", "letter", "kiss", "heart"],
+    "moji": "💌"
+  },
+  "low_brightness": {
+    "unicode": "1F505",
+    "unicode_alternates": [],
+    "name": "low brightness symbol",
+    "shortname": ":low_brightness:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["summer", "sun"],
+    "moji": "🔅"
+  },
+  "m": {
+    "unicode": "24C2",
+    "unicode_alternates": ["24C2-FE0F"],
+    "name": "circled latin capital letter m",
+    "shortname": ":m:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alphabet", "blue-circle", "letter"],
+    "moji": "Ⓜ"
+  },
+  "mag": {
+    "unicode": "1F50D",
+    "unicode_alternates": [],
+    "name": "left-pointing magnifying glass",
+    "shortname": ":mag:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["search", "zoom", "detective", "investigator", "detail", "details"],
+    "moji": "🔍"
+  },
+  "mag_right": {
+    "unicode": "1F50E",
+    "unicode_alternates": [],
+    "name": "right-pointing magnifying glass",
+    "shortname": ":mag_right:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["search", "zoom", "detective", "investigator", "detail", "details"],
+    "moji": "🔎"
+  },
+  "mahjong": {
+    "unicode": "1F004",
+    "unicode_alternates": ["1F004-FE0F"],
+    "name": "mahjong tile red dragon",
+    "shortname": ":mahjong:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "game", "kanji"],
+    "moji": "🀄"
+  },
+  "mailbox": {
+    "unicode": "1F4EB",
+    "unicode_alternates": [],
+    "name": "closed mailbox with raised flag",
+    "shortname": ":mailbox:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["communication", "email", "inbox"],
+    "moji": "📫"
+  },
+  "mailbox_closed": {
+    "unicode": "1F4EA",
+    "unicode_alternates": [],
+    "name": "closed mailbox with lowered flag",
+    "shortname": ":mailbox_closed:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["communication", "email", "inbox"],
+    "moji": "📪"
+  },
+  "mailbox_with_mail": {
+    "unicode": "1F4EC",
+    "unicode_alternates": [],
+    "name": "open mailbox with raised flag",
+    "shortname": ":mailbox_with_mail:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["communication", "email", "inbox"],
+    "moji": "📬"
+  },
+  "mailbox_with_no_mail": {
+    "unicode": "1F4ED",
+    "unicode_alternates": [],
+    "name": "open mailbox with lowered flag",
+    "shortname": ":mailbox_with_no_mail:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["email", "inbox"],
+    "moji": "📭"
+  },
+  "man": {
+    "unicode": "1F468",
+    "unicode_alternates": [],
+    "name": "man",
+    "shortname": ":man:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["classy", "dad", "father", "guy", "mustashe"],
+    "moji": "👨"
+  },
+  "man_with_gua_pi_mao": {
+    "unicode": "1F472",
+    "unicode_alternates": [],
+    "name": "man with gua pi mao",
+    "shortname": ":man_with_gua_pi_mao:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["boy", "male", "skullcap", "chinese", "asian", "qing"],
+    "moji": "👲"
+  },
+  "man_with_turban": {
+    "unicode": "1F473",
+    "unicode_alternates": [],
+    "name": "man with turban",
+    "shortname": ":man_with_turban:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["male", "turban", "headdress", "headwear", "pagri", "india", "indian", "mummy", "wisdom", "peace"],
+    "moji": "👳"
+  },
+  "mans_shoe": {
+    "unicode": "1F45E",
+    "unicode_alternates": [],
+    "name": "mans shoe",
+    "shortname": ":mans_shoe:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fashion", "male"],
+    "moji": "👞"
+  },
+  "map": {
+    "unicode": "1F5FA",
+    "unicode_alternates": [],
+    "name": "world map",
+    "shortname": ":map:",
+    "category": "travel_places",
+    "aliases": [":world_map:"],
+    "aliases_ascii": [],
+    "keywords": ["atlas", "earth", "cartography"]
+  },
+  "maple_leaf": {
+    "unicode": "1F341",
+    "unicode_alternates": [],
+    "name": "maple leaf",
+    "shortname": ":maple_leaf:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["canada", "nature", "plant", "vegetable", "maple", "leaf", "syrup", "canada", "tree"],
+    "moji": "🍁"
+  },
+  "mask": {
+    "unicode": "1F637",
+    "unicode_alternates": [],
+    "name": "face with medical mask",
+    "shortname": ":mask:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "ill", "sick", "sick", "virus", "flu", "medical", "mask"],
+    "moji": "😷"
+  },
+  "massage": {
+    "unicode": "1F486",
+    "unicode_alternates": [],
+    "name": "face massage",
+    "shortname": ":massage:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["female", "girl", "woman"],
+    "moji": "💆"
+  },
+  "meat_on_bone": {
+    "unicode": "1F356",
+    "unicode_alternates": [],
+    "name": "meat on bone",
+    "shortname": ":meat_on_bone:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "good", "meat", "bone", "animal", "cooked"],
+    "moji": "🍖"
+  },
+  "medal": {
+    "unicode": "1F3C5",
+    "unicode_alternates": [],
+    "name": "sports medal",
+    "shortname": ":medal:",
+    "category": "activity",
+    "aliases": [":sports_medal:"],
+    "aliases_ascii": [],
+    "keywords": ["award", "ceremony", "contest", "ftw", "place", "win", "first", "show", "reward", "achievement"]
+  },
+  "mega": {
+    "unicode": "1F4E3",
+    "unicode_alternates": [],
+    "name": "cheering megaphone",
+    "shortname": ":mega:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sound", "speaker", "volume"],
+    "moji": "📣"
+  },
+  "melon": {
+    "unicode": "1F348",
+    "unicode_alternates": [],
+    "name": "melon",
+    "shortname": ":melon:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "fruit", "nature", "melon", "cantaloupe", "honeydew"],
+    "moji": "🍈"
+  },
+  "mens": {
+    "unicode": "1F6B9",
+    "unicode_alternates": [],
+    "name": "mens symbol",
+    "shortname": ":mens:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["restroom", "toilet", "wc", "men", "bathroom", "restroom", "sign", "boy", "male", "avatar"],
+    "moji": "🚹"
+  },
+  "metro": {
+    "unicode": "1F687",
+    "unicode_alternates": [],
+    "name": "metro",
+    "shortname": ":metro:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "mrt", "transportation", "tube", "underground", "metro", "subway", "underground", "train"],
+    "moji": "🚇"
+  },
+  "microphone": {
+    "unicode": "1F3A4",
+    "unicode_alternates": [],
+    "name": "microphone",
+    "shortname": ":microphone:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["PA", "music", "sound", "microphone", "mic", "audio", "sound", "voice", "karaoke"],
+    "moji": "🎤"
+  },
+  "microphone2": {
+    "unicode": "1F399",
+    "unicode_alternates": [],
+    "name": "studio microphone",
+    "shortname": ":microphone2:",
+    "category": "objects_symbols",
+    "aliases": [":studio_microphone:"],
+    "aliases_ascii": [],
+    "keywords": ["mic", "audio", "recording"]
+  },
+  "microscope": {
+    "unicode": "1F52C",
+    "unicode_alternates": [],
+    "name": "microscope",
+    "shortname": ":microscope:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["experiment", "laboratory", "zoomin"],
+    "moji": "🔬"
+  },
+  "middle_finger": {
+    "unicode": "1F595",
+    "unicode_alternates": [],
+    "name": "reversed hand with middle finger extended",
+    "shortname": ":middle_finger:",
+    "category": "people",
+    "aliases": [":reversed_hand_with_middle_finger_extended:"],
+    "aliases_ascii": [],
+    "keywords": ["fu"]
+  },
+  "military_medal": {
+    "unicode": "1F396",
+    "unicode_alternates": [],
+    "name": "military medal",
+    "shortname": ":military_medal:",
+    "category": "celebration",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["honor", "acknowledgment", "purple heart", "heroism", "veteran"]
+  },
+  "milky_way": {
+    "unicode": "1F30C",
+    "unicode_alternates": [],
+    "name": "milky way",
+    "shortname": ":milky_way:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["photo", "space", "milky", "galaxy", "star", "stars", "planets", "space", "sky"],
+    "moji": "🌌"
+  },
+  "minibus": {
+    "unicode": "1F690",
+    "unicode_alternates": [],
+    "name": "minibus",
+    "shortname": ":minibus:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["car", "transportation", "vehicle", "bus", "city", "transport", "transportation"],
+    "moji": "🚐"
+  },
+  "minidisc": {
+    "unicode": "1F4BD",
+    "unicode_alternates": [],
+    "name": "minidisc",
+    "shortname": ":minidisc:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["data", "disc", "disk", "record", "technology"],
+    "moji": "💽"
+  },
+  "mobile_phone_off": {
+    "unicode": "1F4F4",
+    "unicode_alternates": [],
+    "name": "mobile phone off",
+    "shortname": ":mobile_phone_off:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["mute"],
+    "moji": "📴"
+  },
+  "money_with_wings": {
+    "unicode": "1F4B8",
+    "unicode_alternates": [],
+    "name": "money with wings",
+    "shortname": ":money_with_wings:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bills", "dollar", "payment", "money", "wings", "easy", "spend", "work", "lost", "blown", "burned", "gift", "cash", "dollar"],
+    "moji": "💸"
+  },
+  "moneybag": {
+    "unicode": "1F4B0",
+    "unicode_alternates": [],
+    "name": "money bag",
+    "shortname": ":moneybag:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["coins", "dollar", "payment"],
+    "moji": "💰"
+  },
+  "monkey": {
+    "unicode": "1F412",
+    "unicode_alternates": [],
+    "name": "monkey",
+    "shortname": ":monkey:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "monkey", "primate", "banana", "silly"],
+    "moji": "🐒"
+  },
+  "monkey_face": {
+    "unicode": "1F435",
+    "unicode_alternates": [],
+    "name": "monkey face",
+    "shortname": ":monkey_face:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature"],
+    "moji": "🐵"
+  },
+  "monorail": {
+    "unicode": "1F69D",
+    "unicode_alternates": [],
+    "name": "monorail",
+    "shortname": ":monorail:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle", "train", "mono", "rail", "transport"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cap", "college", "degree", "graduation", "hat", "school", "university", "graduation", "cap", "mortarboard", "academic", "education", "ceremony", "square", "tassel"],
+    "moji": "🎓"
+  },
+  "motorboat": {
+    "unicode": "1F6E5",
+    "unicode_alternates": [],
+    "name": "motorboat",
+    "shortname": ":motorboat:",
+    "category": "travel_places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle", "boat", "speedboat", "powerboat"]
+  },
+  "motorcycle": {
+    "unicode": "1F3CD",
+    "unicode_alternates": [],
+    "name": "racing motorcycle",
+    "shortname": ":motorcycle:",
+    "category": "activity",
+    "aliases": [":racing_motorcycle:"],
+    "aliases_ascii": [],
+    "keywords": ["bike", "speed"]
+  },
+  "motorway": {
+    "unicode": "1F6E3",
+    "unicode_alternates": [],
+    "name": "motorway",
+    "shortname": ":motorway:",
+    "category": "travel_places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["road", "highway", "freeway", "traffic", "travel"]
+  },
+  "mount_fuji": {
+    "unicode": "1F5FB",
+    "unicode_alternates": [],
+    "name": "mount fuji",
+    "shortname": ":mount_fuji:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["japan", "mountain", "nature", "photo"],
+    "moji": "🗻"
+  },
+  "mountain_bicyclist": {
+    "unicode": "1F6B5",
+    "unicode_alternates": [],
+    "name": "mountain bicyclist",
+    "shortname": ":mountain_bicyclist:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["human", "sports", "transportation", "bicyclist", "mountain", "bike", "pedal", "bicycle", "transportation"],
+    "moji": "🚵"
+  },
+  "mountain_cableway": {
+    "unicode": "1F6A0",
+    "unicode_alternates": [],
+    "name": "mountain cableway",
+    "shortname": ":mountain_cableway:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle", "mountain", "cable", "rail", "train", "railway"],
+    "moji": "🚠"
+  },
+  "mountain_railway": {
+    "unicode": "1F69E",
+    "unicode_alternates": [],
+    "name": "mountain railway",
+    "shortname": ":mountain_railway:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "mountain", "railway", "rail", "train", "transport"],
+    "moji": "🚞"
+  },
+  "mountain_snow": {
+    "unicode": "1F3D4",
+    "unicode_alternates": [],
+    "name": "snow capped mountain",
+    "shortname": ":mountain_snow:",
+    "category": "travel_places",
+    "aliases": [":snow_capped_mountain:"],
+    "aliases_ascii": [],
+    "keywords": ["cold", "elevation", "hiking", "peak"]
+  },
+  "mouse": {
+    "unicode": "1F42D",
+    "unicode_alternates": [],
+    "name": "mouse face",
+    "shortname": ":mouse:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature"],
+    "moji": "🐭"
+  },
+  "mouse2": {
+    "unicode": "1F401",
+    "unicode_alternates": [],
+    "name": "mouse",
+    "shortname": ":mouse2:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "mouse", "mice", "rodent"],
+    "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"]
+  },
+  "movie_camera": {
+    "unicode": "1F3A5",
+    "unicode_alternates": [],
+    "name": "movie camera",
+    "shortname": ":movie_camera:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["film", "record", "movie", "camera", "camcorder", "video", "motion", "picture"],
+    "moji": "🎥"
+  },
+  "moyai": {
+    "unicode": "1F5FF",
+    "unicode_alternates": [],
+    "name": "moyai",
+    "shortname": ":moyai:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["island", "stone"],
+    "moji": "🗿"
+  },
+  "muscle": {
+    "unicode": "1F4AA",
+    "unicode_alternates": [],
+    "name": "flexed biceps",
+    "shortname": ":muscle:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arm", "flex", "hand", "strong", "muscle", "bicep"],
+    "moji": "💪"
+  },
+  "mushroom": {
+    "unicode": "1F344",
+    "unicode_alternates": [],
+    "name": "mushroom",
+    "shortname": ":mushroom:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["plant", "vegetable", "mushroom", "fungi", "food", "fungus"],
+    "moji": "🍄"
+  },
+  "musical_keyboard": {
+    "unicode": "1F3B9",
+    "unicode_alternates": [],
+    "name": "musical keyboard",
+    "shortname": ":musical_keyboard:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["instrument", "piano", "music", "keyboard", "piano", "organ", "instrument", "electric"],
+    "moji": "🎹"
+  },
+  "musical_note": {
+    "unicode": "1F3B5",
+    "unicode_alternates": [],
+    "name": "musical note",
+    "shortname": ":musical_note:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["score", "musical", "music", "note", "music", "sound"],
+    "moji": "🎵"
+  },
+  "musical_score": {
+    "unicode": "1F3BC",
+    "unicode_alternates": [],
+    "name": "musical score",
+    "shortname": ":musical_score:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["clef", "treble", "music", "musical", "score", "clef", "g-clef", "stave", "staff"],
+    "moji": "🎼"
+  },
+  "mute": {
+    "unicode": "1F507",
+    "unicode_alternates": [],
+    "name": "speaker with cancellation stroke",
+    "shortname": ":mute:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sound", "volume"],
+    "moji": "🔇"
+  },
+  "nail_care": {
+    "unicode": "1F485",
+    "unicode_alternates": [],
+    "name": "nail polish",
+    "shortname": ":nail_care:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["beauty", "manicure"],
+    "moji": "💅"
+  },
+  "name_badge": {
+    "unicode": "1F4DB",
+    "unicode_alternates": [],
+    "name": "name badge",
+    "shortname": ":name_badge:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fire", "forbid"],
+    "moji": "📛"
+  },
+  "necktie": {
+    "unicode": "1F454",
+    "unicode_alternates": [],
+    "name": "necktie",
+    "shortname": ":necktie:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cloth", "fashion", "formal", "shirt", "suitup"],
+    "moji": "👔"
+  },
+  "negative_squared_cross_mark": {
+    "unicode": "274E",
+    "unicode_alternates": [],
+    "name": "negative squared cross mark",
+    "shortname": ":negative_squared_cross_mark:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["deny", "green-square", "no", "x"],
+    "moji": "❎"
+  },
+  "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"]
+  },
+  "neutral_face": {
+    "unicode": "1F610",
+    "unicode_alternates": [],
+    "name": "neutral face",
+    "shortname": ":neutral_face:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "indifference", "neutral", "objective", "impartial", "blank"],
+    "moji": "😐"
+  },
+  "new": {
+    "unicode": "1F195",
+    "unicode_alternates": [],
+    "name": "squared new",
+    "shortname": ":new:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square"],
+    "moji": "🆕"
+  },
+  "new_moon": {
+    "unicode": "1F311",
+    "unicode_alternates": [],
+    "name": "new moon symbol",
+    "shortname": ":new_moon:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "moon", "new", "sky", "night", "cheese", "phase"],
+    "moji": "🌑"
+  },
+  "new_moon_with_face": {
+    "unicode": "1F31A",
+    "unicode_alternates": [],
+    "name": "new moon with face",
+    "shortname": ":new_moon_with_face:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "moon", "new", "anthropomorphic", "face", "sky", "night", "cheese", "phase"],
+    "moji": "🌚"
+  },
+  "newspaper": {
+    "unicode": "1F4F0",
+    "unicode_alternates": [],
+    "name": "newspaper",
+    "shortname": ":newspaper:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["headline", "press"],
+    "moji": "📰"
+  },
+  "newspaper2": {
+    "unicode": "1F5DE",
+    "unicode_alternates": [],
+    "name": "rolled-up newspaper",
+    "shortname": ":newspaper2:",
+    "category": "objects_symbols",
+    "aliases": [":rolled_up_newspaper:"],
+    "aliases_ascii": [],
+    "keywords": ["headline", "press"]
+  },
+  "night_with_stars": {
+    "unicode": "1F303",
+    "unicode_alternates": [],
+    "name": "night with stars",
+    "shortname": ":night_with_stars:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["night", "star", "cloudless", "evening", "planets", "space", "sky"],
+    "moji": "🌃"
+  },
+  "nine": {
+    "moji": "9️⃣",
+    "unicode": "0039-20E3",
+    "unicode_alternates": ["0039-FE0F-20E3"],
+    "name": "digit nine",
+    "shortname": ":nine:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["9", "blue-square", "numbers"]
+  },
+  "no_bell": {
+    "unicode": "1F515",
+    "unicode_alternates": [],
+    "name": "bell with cancellation stroke",
+    "shortname": ":no_bell:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["mute", "sound", "volume"],
+    "moji": "🔕"
+  },
+  "no_bicycles": {
+    "unicode": "1F6B3",
+    "unicode_alternates": [],
+    "name": "no bicycles",
+    "shortname": ":no_bicycles:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cyclist", "prohibited", "bicycle", "bike pedal", "no"],
+    "moji": "🚳"
+  },
+  "no_entry": {
+    "unicode": "26D4",
+    "unicode_alternates": ["26D4-FE0F"],
+    "name": "no entry",
+    "shortname": ":no_entry:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bad", "denied", "limit", "privacy", "security", "stop"],
+    "moji": "⛔"
+  },
+  "no_entry_sign": {
+    "unicode": "1F6AB",
+    "unicode_alternates": [],
+    "name": "no entry sign",
+    "shortname": ":no_entry_sign:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["denied", "disallow", "forbid", "limit", "stop", "no", "stop", "entry"],
+    "moji": "🚫"
+  },
+  "no_good": {
+    "unicode": "1F645",
+    "unicode_alternates": [],
+    "name": "face with no good gesture",
+    "shortname": ":no_good:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["female", "girl", "woman", "no", "stop", "nope", "don&#039;t", "not"],
+    "moji": "🙅"
+  },
+  "no_mobile_phones": {
+    "unicode": "1F4F5",
+    "unicode_alternates": [],
+    "name": "no mobile phones",
+    "shortname": ":no_mobile_phones:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["iphone", "mute"],
+    "moji": "📵"
+  },
+  "no_mouth": {
+    "unicode": "1F636",
+    "unicode_alternates": [],
+    "name": "face without mouth",
+    "shortname": ":no_mouth:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [":-X", ":X", ":-#", ":#", "=X", "=x", ":x", ":-x", "=#"],
+    "keywords": ["face", "hellokitty", "mouth", "silent", "vapid"],
+    "moji": "😶"
+  },
+  "no_pedestrians": {
+    "unicode": "1F6B7",
+    "unicode_alternates": [],
+    "name": "no pedestrians",
+    "shortname": ":no_pedestrians:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["crossing", "rules", "walking", "no", "walk", "pedestrian", "stroll", "stride", "foot", "feet"],
+    "moji": "🚷"
+  },
+  "no_smoking": {
+    "unicode": "1F6AD",
+    "unicode_alternates": [],
+    "name": "no smoking symbol",
+    "shortname": ":no_smoking:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cigarette", "no", "smoking", "cigarette", "smoke", "cancer", "lungs", "inhale", "tar", "nicotine"],
+    "moji": "🚭"
+  },
+  "non-potable_water": {
+    "unicode": "1F6B1",
+    "unicode_alternates": [],
+    "name": "non-potable water symbol",
+    "shortname": ":non-potable_water:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["drink", "faucet", "tap", "non-potable", "water", "not drinkable", "dirty", "gross", "aqua", "h20"],
+    "moji": "🚱"
+  },
+  "nose": {
+    "unicode": "1F443",
+    "unicode_alternates": [],
+    "name": "nose",
+    "shortname": ":nose:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["smell", "sniff"],
+    "moji": "👃"
+  },
+  "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"]
+  },
+  "notebook": {
+    "unicode": "1F4D3",
+    "unicode_alternates": [],
+    "name": "notebook",
+    "shortname": ":notebook:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["notes", "paper", "record", "stationery"],
+    "moji": "📓"
+  },
+  "notebook_with_decorative_cover": {
+    "unicode": "1F4D4",
+    "unicode_alternates": [],
+    "name": "notebook with decorative cover",
+    "shortname": ":notebook_with_decorative_cover:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["classroom", "notes", "paper", "record"],
+    "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",
+    "aliases": [":spiral_note_pad:"],
+    "aliases_ascii": [],
+    "keywords": ["stationery"]
+  },
+  "notes": {
+    "unicode": "1F3B6",
+    "unicode_alternates": [],
+    "name": "multiple musical notes",
+    "shortname": ":notes:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["music", "score", "musical", "music", "notes", "music", "sound", "melody"],
+    "moji": "🎶"
+  },
+  "nut_and_bolt": {
+    "unicode": "1F529",
+    "unicode_alternates": [],
+    "name": "nut and bolt",
+    "shortname": ":nut_and_bolt:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["handy", "tools"],
+    "moji": "🔩"
+  },
+  "o": {
+    "unicode": "2B55",
+    "unicode_alternates": ["2B55-FE0F"],
+    "name": "heavy large circle",
+    "shortname": ":o:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["circle", "round"],
+    "moji": "⭕"
+  },
+  "o2": {
+    "unicode": "1F17E",
+    "unicode_alternates": [],
+    "name": "negative squared latin capital letter o",
+    "shortname": ":o2:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alphabet", "letter", "red-square"],
+    "moji": "🅾"
+  },
+  "ocean": {
+    "unicode": "1F30A",
+    "unicode_alternates": [],
+    "name": "water wave",
+    "shortname": ":ocean:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sea", "water", "wave", "ocean", "wave", "surf", "beach", "tide"],
+    "moji": "🌊"
+  },
+  "octopus": {
+    "unicode": "1F419",
+    "unicode_alternates": [],
+    "name": "octopus",
+    "shortname": ":octopus:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "creature", "ocean", "sea"],
+    "moji": "🐙"
+  },
+  "oden": {
+    "unicode": "1F362",
+    "unicode_alternates": [],
+    "name": "oden",
+    "shortname": ":oden:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "japanese", "oden", "seafood", "casserole", "stew"],
+    "moji": "🍢"
+  },
+  "office": {
+    "unicode": "1F3E2",
+    "unicode_alternates": [],
+    "name": "office building",
+    "shortname": ":office:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building", "bureau", "work"],
+    "moji": "🏢"
+  },
+  "oil": {
+    "unicode": "1F6E2",
+    "unicode_alternates": [],
+    "name": "oil drum",
+    "shortname": ":oil:",
+    "category": "objects_symbols",
+    "aliases": [":oil_drum:"],
+    "aliases_ascii": [],
+    "keywords": ["petroleum"]
+  },
+  "ok": {
+    "unicode": "1F197",
+    "unicode_alternates": [],
+    "name": "squared ok",
+    "shortname": ":ok:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["agree", "blue-square", "good", "yes"],
+    "moji": "🆗"
+  },
+  "ok_hand": {
+    "unicode": "1F44C",
+    "unicode_alternates": [],
+    "name": "ok hand sign",
+    "shortname": ":ok_hand:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fingers", "limbs", "perfect", "okay", "ok", "smoke", "smoking", "marijuana", "joint", "pot", "420"],
+    "moji": "👌"
+  },
+  "ok_woman": {
+    "unicode": "1F646",
+    "unicode_alternates": [],
+    "name": "face with ok gesture",
+    "shortname": ":ok_woman:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": ["*\\0/*", "\\0/", "*\\O/*", "\\O/"],
+    "keywords": ["female", "girl", "human", "pink", "women", "yes", "ok", "okay", "accept"],
+    "moji": "🙆"
+  },
+  "older_man": {
+    "unicode": "1F474",
+    "unicode_alternates": [],
+    "name": "older man",
+    "shortname": ":older_man:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["human", "male", "men"],
+    "moji": "👴"
+  },
+  "older_woman": {
+    "unicode": "1F475",
+    "unicode_alternates": [],
+    "name": "older woman",
+    "shortname": ":older_woman:",
+    "category": "emoticons",
+    "aliases": [":grandma:"],
+    "aliases_ascii": [],
+    "keywords": ["female", "girl", "women", "grandma", "grandmother"],
+    "moji": "👵"
+  },
+  "om_symbol": {
+    "unicode": "1F549",
+    "unicode_alternates": [],
+    "name": "om symbol",
+    "shortname": ":om_symbol:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["hinduism", "sound", "spiritual", "icon", "dharmic", "buddhism", "jainism", "meditate"]
+  },
+  "on": {
+    "unicode": "1F51B",
+    "unicode_alternates": [],
+    "name": "on with exclamation mark with left right arrow abo",
+    "shortname": ":on:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "words"],
+    "moji": "🔛"
+  },
+  "oncoming_automobile": {
+    "unicode": "1F698",
+    "unicode_alternates": [],
+    "name": "oncoming automobile",
+    "shortname": ":oncoming_automobile:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["car", "transportation", "vehicle", "sedan", "car", "automobile"],
+    "moji": "🚘"
+  },
+  "oncoming_bus": {
+    "unicode": "1F68D",
+    "unicode_alternates": [],
+    "name": "oncoming bus",
+    "shortname": ":oncoming_bus:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle", "bus", "school", "city", "transportation", "public"],
+    "moji": "🚍"
+  },
+  "oncoming_police_car": {
+    "unicode": "1F694",
+    "unicode_alternates": [],
+    "name": "oncoming police car",
+    "shortname": ":oncoming_police_car:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["enforcement", "law", "vehicle", "police", "car", "emergency", "ticket", "citation", "crime", "help", "officer"],
+    "moji": "🚔"
+  },
+  "oncoming_taxi": {
+    "unicode": "1F696",
+    "unicode_alternates": [],
+    "name": "oncoming taxi",
+    "shortname": ":oncoming_taxi:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cars", "uber", "vehicle", "taxi", "car", "automobile", "city", "transport", "service"],
+    "moji": "🚖"
+  },
+  "one": {
+    "moji": "1️⃣",
+    "unicode": "0031-20E3",
+    "unicode_alternates": ["0031-FE0F-20E3"],
+    "name": "digit one",
+    "shortname": ":one:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["1", "blue-square", "numbers"]
+  },
+  "open_file_folder": {
+    "unicode": "1F4C2",
+    "unicode_alternates": [],
+    "name": "open file folder",
+    "shortname": ":open_file_folder:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["documents", "load"],
+    "moji": "📂"
+  },
+  "open_hands": {
+    "unicode": "1F450",
+    "unicode_alternates": [],
+    "name": "open hands sign",
+    "shortname": ":open_hands:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["butterfly", "fingers"],
+    "moji": "👐"
+  },
+  "open_mouth": {
+    "unicode": "1F62E",
+    "unicode_alternates": [],
+    "name": "face with open mouth",
+    "shortname": ":open_mouth:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [":-O", ":O", ":-o", ":o", "O_O", ">:O"],
+    "keywords": ["face", "impressed", "mouth", "open", "jaw", "gapping", "surprise", "wow"],
+    "moji": "😮"
+  },
+  "ophiuchus": {
+    "unicode": "26CE",
+    "unicode_alternates": [],
+    "name": "ophiuchus",
+    "shortname": ":ophiuchus:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["ophiuchus", "serpent", "snake", "astrology", "greek", "constellation", "stars", "zodiac", "purple-square", "sign", "horoscope"],
+    "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": [],
+    "name": "orange book",
+    "shortname": ":orange_book:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["knowledge", "library", "read"],
+    "moji": "📙"
+  },
+  "outbox_tray": {
+    "unicode": "1F4E4",
+    "unicode_alternates": [],
+    "name": "outbox tray",
+    "shortname": ":outbox_tray:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["email", "inbox"],
+    "moji": "📤"
+  },
+  "ox": {
+    "unicode": "1F402",
+    "unicode_alternates": [],
+    "name": "ox",
+    "shortname": ":ox:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "beef", "cow"],
+    "moji": "🐂"
+  },
+  "package": {
+    "unicode": "1F4E6",
+    "unicode_alternates": [],
+    "name": "package",
+    "shortname": ":package:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["gift", "mail"],
+    "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": [],
+    "name": "page facing up",
+    "shortname": ":page_facing_up:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["documents"],
+    "moji": "📄"
+  },
+  "page_with_curl": {
+    "unicode": "1F4C3",
+    "unicode_alternates": [],
+    "name": "page with curl",
+    "shortname": ":page_with_curl:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["documents"],
+    "moji": "📃"
+  },
+  "pager": {
+    "unicode": "1F4DF",
+    "unicode_alternates": [],
+    "name": "pager",
+    "shortname": ":pager:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bbcall", "oldschool"],
+    "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",
+    "aliases": [":lower_left_paintbrush:"],
+    "aliases_ascii": [],
+    "keywords": ["brush", "art", "painting"]
+  },
+  "palm_tree": {
+    "unicode": "1F334",
+    "unicode_alternates": [],
+    "name": "palm tree",
+    "shortname": ":palm_tree:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "plant", "vegetable", "palm", "tree", "coconuts", "fronds", "warm", "tropical"],
+    "moji": "🌴"
+  },
+  "panda_face": {
+    "unicode": "1F43C",
+    "unicode_alternates": [],
+    "name": "panda face",
+    "shortname": ":panda_face:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "panda", "bear", "face", "cub", "cute", "endearment", "friendship", "love", "bamboo", "china", "black", "white"],
+    "moji": "🐼"
+  },
+  "paperclip": {
+    "unicode": "1F4CE",
+    "unicode_alternates": [],
+    "name": "paperclip",
+    "shortname": ":paperclip:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["documents", "stationery"],
+    "moji": "📎"
+  },
+  "paperclips": {
+    "unicode": "1F587",
+    "unicode_alternates": [],
+    "name": "linked paperclips",
+    "shortname": ":paperclips:",
+    "category": "objects_symbols",
+    "aliases": [":linked_paperclips:"],
+    "aliases_ascii": [],
+    "keywords": ["documents", "stationery"]
+  },
+  "park": {
+    "unicode": "1F3DE",
+    "unicode_alternates": [],
+    "name": "national park",
+    "shortname": ":park:",
+    "category": "travel_places",
+    "aliases": [":national_park:"],
+    "aliases_ascii": [],
+    "keywords": ["woods", "nature", "wildlife", "forest", "wilderness", "national"]
+  },
+  "parking": {
+    "unicode": "1F17F",
+    "unicode_alternates": ["1F17F-FE0F"],
+    "name": "negative squared latin capital letter p",
+    "shortname": ":parking:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alphabet", "blue-square", "cars", "letter"],
+    "moji": "🅿"
+  },
+  "part_alternation_mark": {
+    "unicode": "303D",
+    "unicode_alternates": ["303D-FE0F"],
+    "name": "part alternation mark",
+    "shortname": ":part_alternation_mark:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["graph", "sing", "song", "vocal", "music", "karaoke", "cue", "letter", "m", "japanese"],
+    "moji": "〽"
+  },
+  "partly_sunny": {
+    "unicode": "26C5",
+    "unicode_alternates": ["26C5-FE0F"],
+    "name": "sun behind cloud",
+    "shortname": ":partly_sunny:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cloud", "morning", "nature", "weather"],
+    "moji": "⛅"
+  },
+  "passport_control": {
+    "unicode": "1F6C2",
+    "unicode_alternates": [],
+    "name": "passport control",
+    "shortname": ":passport_control:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "custom", "passport", "official", "travel", "control", "foreign", "identification"],
+    "moji": "🛂"
+  },
+  "peach": {
+    "unicode": "1F351",
+    "unicode_alternates": [],
+    "name": "peach",
+    "shortname": ":peach:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "fruit", "nature", "peach", "fruit", "juicy", "pit"],
+    "moji": "🍑"
+  },
+  "pear": {
+    "unicode": "1F350",
+    "unicode_alternates": [],
+    "name": "pear",
+    "shortname": ":pear:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fruit", "nature", "pear", "fruit", "shape"],
+    "moji": "🍐"
+  },
+  "pen_ballpoint": {
+    "unicode": "1F58A",
+    "unicode_alternates": [],
+    "name": "lower left ballpoint pen",
+    "shortname": ":pen_ballpoint:",
+    "category": "objects_symbols",
+    "aliases": [":lower_left_ballpoint_pen:"],
+    "aliases_ascii": [],
+    "keywords": ["write", "bic", "ink"]
+  },
+  "pen_fountain": {
+    "unicode": "1F58B",
+    "unicode_alternates": [],
+    "name": "lower left fountain pen",
+    "shortname": ":pen_fountain:",
+    "category": "objects_symbols",
+    "aliases": [":lower_left_fountain_pen:"],
+    "aliases_ascii": [],
+    "keywords": ["write", "calligraphy", "ink"]
+  },
+  "pencil": {
+    "unicode": "1F4DD",
+    "unicode_alternates": [],
+    "name": "memo",
+    "shortname": ":pencil:",
+    "category": "objects",
+    "aliases": [":memo:"],
+    "aliases_ascii": [],
+    "keywords": ["documents", "paper", "station", "write"],
+    "moji": "📝"
+  },
+  "pencil2": {
+    "unicode": "270F",
+    "unicode_alternates": ["270F-FE0F"],
+    "name": "pencil",
+    "shortname": ":pencil2:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["paper", "stationery", "write"],
+    "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": [],
+    "name": "penguin",
+    "shortname": ":penguin:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "okay", "sad", "pensive", "thoughtful", "think", "reflective", "wistful", "meditate", "serious"],
+    "moji": "😔"
+  },
+  "performing_arts": {
+    "unicode": "1F3AD",
+    "unicode_alternates": [],
+    "name": "performing arts",
+    "shortname": ":performing_arts:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["acting", "drama", "theater", "performing", "arts", "performance", "entertainment", "acting", "story", "mask", "masks"],
+    "moji": "🎭"
+  },
+  "persevere": {
+    "unicode": "1F623",
+    "unicode_alternates": [],
+    "name": "persevering face",
+    "shortname": ":persevere:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [">.<"],
+    "keywords": ["endure", "persevere", "face", "no", "sick", "upset"],
+    "moji": "😣"
+  },
+  "person_frowning": {
+    "unicode": "1F64D",
+    "unicode_alternates": [],
+    "name": "person frowning",
+    "shortname": ":person_frowning:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["female", "girl", "woman", "dejected", "rejected", "sad", "frown"],
+    "moji": "🙍"
+  },
+  "person_with_blond_hair": {
+    "unicode": "1F471",
+    "unicode_alternates": [],
+    "name": "person with blond hair",
+    "shortname": ":person_with_blond_hair:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["male", "man", "blonde", "young", "western", "westerner", "occidental"],
+    "moji": "👱"
+  },
+  "person_with_pouting_face": {
+    "unicode": "1F64E",
+    "unicode_alternates": [],
+    "name": "person with pouting face",
+    "shortname": ":person_with_pouting_face:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["female", "girl", "woman", "pout", "sexy", "cute", "annoyed"],
+    "moji": "🙎"
+  },
+  "pig": {
+    "unicode": "1F437",
+    "unicode_alternates": [],
+    "name": "pig face",
+    "shortname": ":pig:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "oink"],
+    "moji": "🐷"
+  },
+  "pig2": {
+    "unicode": "1F416",
+    "unicode_alternates": [],
+    "name": "pig",
+    "shortname": ":pig2:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "pig", "piggy", "pork", "ham", "hog", "bacon", "oink", "slop", "livestock", "greed", "greedy"],
+    "moji": "🐖"
+  },
+  "pig_nose": {
+    "unicode": "1F43D",
+    "unicode_alternates": [],
+    "name": "pig nose",
+    "shortname": ":pig_nose:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "oink", "pig", "nose", "snout", "food", "eat", "cute", "oink", "pink", "smell", "truffle"],
+    "moji": "🐽"
+  },
+  "pill": {
+    "unicode": "1F48A",
+    "unicode_alternates": [],
+    "name": "pill",
+    "shortname": ":pill:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["health", "medicine"],
+    "moji": "💊"
+  },
+  "pineapple": {
+    "unicode": "1F34D",
+    "unicode_alternates": [],
+    "name": "pineapple",
+    "shortname": ":pineapple:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "fruit", "nature", "pineapple", "pina", "tropical", "flower"],
+    "moji": "🍍"
+  },
+  "piracy": {
+    "unicode": "1F572",
+    "unicode_alternates": [],
+    "name": "no piracy",
+    "shortname": ":piracy:",
+    "category": "objects_symbols",
+    "aliases": [":no_piracy:"],
+    "aliases_ascii": [],
+    "keywords": ["theft", "rule"]
+  },
+  "pisces": {
+    "unicode": "2653",
+    "unicode_alternates": ["2653-FE0F"],
+    "name": "pisces",
+    "shortname": ":pisces:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["pisces", "fish", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+    "moji": "♓"
+  },
+  "pizza": {
+    "unicode": "1F355",
+    "unicode_alternates": [],
+    "name": "slice of pizza",
+    "shortname": ":pizza:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "party", "pizza", "pie", "new york", "italian", "italy", "slice", "peperoni"],
+    "moji": "🍕"
+  },
+  "point_down": {
+    "unicode": "1F447",
+    "unicode_alternates": [],
+    "name": "white down pointing backhand index",
+    "shortname": ":point_down:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["direction", "fingers", "hand"],
+    "moji": "👇"
+  },
+  "point_left": {
+    "unicode": "1F448",
+    "unicode_alternates": [],
+    "name": "white left pointing backhand index",
+    "shortname": ":point_left:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["direction", "fingers", "hand"],
+    "moji": "👈"
+  },
+  "point_right": {
+    "unicode": "1F449",
+    "unicode_alternates": [],
+    "name": "white right pointing backhand index",
+    "shortname": ":point_right:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["direction", "fingers", "hand"],
+    "moji": "👉"
+  },
+  "point_up": {
+    "unicode": "261D",
+    "unicode_alternates": ["261D-FE0F"],
+    "name": "white up pointing index",
+    "shortname": ":point_up:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["direction", "fingers", "hand"],
+    "moji": "☝"
+  },
+  "point_up_2": {
+    "unicode": "1F446",
+    "unicode_alternates": [],
+    "name": "white up pointing backhand index",
+    "shortname": ":point_up_2:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["direction", "fingers", "hand"],
+    "moji": "👆"
+  },
+  "police_car": {
+    "unicode": "1F693",
+    "unicode_alternates": [],
+    "name": "police car",
+    "shortname": ":police_car:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cars", "enforcement", "law", "transportation", "vehicle", "police", "car", "emergency", "ticket", "citation", "crime", "help", "officer"],
+    "moji": "🚓"
+  },
+  "poodle": {
+    "unicode": "1F429",
+    "unicode_alternates": [],
+    "name": "poodle",
+    "shortname": ":poodle:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["101", "animal", "dog", "nature", "poodle", "dog", "clip", "showy", "sophisticated", "vain"],
+    "moji": "🐩"
+  },
+  "poop": {
+    "unicode": "1F4A9",
+    "unicode_alternates": [],
+    "name": "pile of poo",
+    "shortname": ":poop:",
+    "category": "emoticons",
+    "aliases": [":shit:", ":hankey:", ":poo:"],
+    "aliases_ascii": [],
+    "keywords": ["poop", "shit", "shitface", "turd", "poo"],
+    "moji": "💩"
+  },
+  "post_office": {
+    "unicode": "1F3E3",
+    "unicode_alternates": [],
+    "name": "japanese post office",
+    "shortname": ":post_office:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building", "communication", "email"],
+    "moji": "🏣"
+  },
+  "postal_horn": {
+    "unicode": "1F4EF",
+    "unicode_alternates": [],
+    "name": "postal horn",
+    "shortname": ":postal_horn:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["instrument", "music"],
+    "moji": "📯"
+  },
+  "postbox": {
+    "unicode": "1F4EE",
+    "unicode_alternates": [],
+    "name": "postbox",
+    "shortname": ":postbox:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["email", "envelope", "letter"],
+    "moji": "📮"
+  },
+  "potable_water": {
+    "unicode": "1F6B0",
+    "unicode_alternates": [],
+    "name": "potable water symbol",
+    "shortname": ":potable_water:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "cleaning", "faucet", "liquid", "restroom", "potable", "water", "drinkable", "pure", "clear", "clean", "aqua", "h20"],
+    "moji": "🚰"
+  },
+  "pouch": {
+    "unicode": "1F45D",
+    "unicode_alternates": [],
+    "name": "pouch",
+    "shortname": ":pouch:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["accessories", "bag", "pouch", "bag", "cosmetic", "packing", "grandma", "makeup"],
+    "moji": "👝"
+  },
+  "poultry_leg": {
+    "unicode": "1F357",
+    "unicode_alternates": [],
+    "name": "poultry leg",
+    "shortname": ":poultry_leg:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "meat", "poultry", "leg", "chicken", "fried"],
+    "moji": "🍗"
+  },
+  "pound": {
+    "unicode": "1F4B7",
+    "unicode_alternates": [],
+    "name": "banknote with pound sign",
+    "shortname": ":pound:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bills", "british", "currency", "england", "money", "sterling", "uk", "pound", "britain", "british", "banknote", "money", "currency", "paper", "cash", "bills"],
+    "moji": "💷"
+  },
+  "pouting_cat": {
+    "unicode": "1F63E",
+    "unicode_alternates": [],
+    "name": "pouting cat face",
+    "shortname": ":pouting_cat:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "cats", "pout", "annoyed", "miffed", "glower", "frown"],
+    "moji": "😾"
+  },
+  "pray": {
+    "unicode": "1F64F",
+    "unicode_alternates": [],
+    "name": "person with folded hands",
+    "shortname": ":pray:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["highfive", "hope", "namaste", "please", "wish", "pray", "high five", "hands", "sorrow", "regret", "sorry"],
+    "moji": "🙏"
+  },
+  "princess": {
+    "unicode": "1F478",
+    "unicode_alternates": [],
+    "name": "princess",
+    "shortname": ":princess:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blond", "crown", "female", "girl", "woman", "princess", "royal", "royalty", "king", "queen", "daughter", "disney", "high-maintenance"],
+    "moji": "👸"
+  },
+  "printer": {
+    "unicode": "1F5A8",
+    "unicode_alternates": [],
+    "name": "printer",
+    "shortname": ":printer:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["hardcopy", "paper", "inkjet", "laser"]
+  },
+  "prohibited": {
+    "unicode": "1F6C7",
+    "unicode_alternates": [],
+    "name": "prohibited sign",
+    "shortname": ":prohibited:",
+    "category": "objects_symbols",
+    "aliases": [":prohibited_sign:"],
+    "aliases_ascii": [],
+    "keywords": ["no", "not", "denied", "disallow", "forbid", "limit", "stop"]
+  },
+  "projector": {
+    "unicode": "1F4FD",
+    "unicode_alternates": [],
+    "name": "film projector",
+    "shortname": ":projector:",
+    "category": "objects_symbols",
+    "aliases": [":film_projector:"],
+    "aliases_ascii": [],
+    "keywords": ["movie", "video", "motion", "picture", "8mm", "16mm"]
+  },
+  "punch": {
+    "unicode": "1F44A",
+    "unicode_alternates": [],
+    "name": "fisted hand sign",
+    "shortname": ":punch:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fist", "hand"],
+    "moji": "👊"
+  },
+  "purple_heart": {
+    "unicode": "1F49C",
+    "unicode_alternates": [],
+    "name": "purple heart",
+    "shortname": ":purple_heart:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "like", "love", "valentines", "purple", "violet", "heart", "love", "sensitive", "understanding", "compassionate", "compassion", "duty", "honor", "royalty", "veteran", "sacrifice"],
+    "moji": "💜"
+  },
+  "purse": {
+    "unicode": "1F45B",
+    "unicode_alternates": [],
+    "name": "purse",
+    "shortname": ":purse:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["accessories", "fashion", "money", "purse", "clutch", "bag", "handbag", "coin bag", "accessory", "money", "ladies", "shopping"],
+    "moji": "👛"
+  },
+  "pushpin": {
+    "unicode": "1F4CC",
+    "unicode_alternates": [],
+    "name": "pushpin",
+    "shortname": ":pushpin:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["stationery"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "litter", "waste", "trash", "garbage", "receptacle", "can"],
+    "moji": "🚮"
+  },
+  "question": {
+    "unicode": "2753",
+    "unicode_alternates": [],
+    "name": "black question mark ornament",
+    "shortname": ":question:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["confused", "doubt"],
+    "moji": "❓"
+  },
+  "rabbit": {
+    "unicode": "1F430",
+    "unicode_alternates": [],
+    "name": "rabbit face",
+    "shortname": ":rabbit:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature"],
+    "moji": "🐰"
+  },
+  "rabbit2": {
+    "unicode": "1F407",
+    "unicode_alternates": [],
+    "name": "rabbit",
+    "shortname": ":rabbit2:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "rabbit", "bunny", "easter", "reproduction", "prolific"],
+    "moji": "🐇"
+  },
+  "race_car": {
+    "unicode": "1F3CE",
+    "unicode_alternates": [],
+    "name": "racing car",
+    "shortname": ":race_car:",
+    "category": "activity",
+    "aliases": [":racing_car:"],
+    "aliases_ascii": [],
+    "keywords": ["formula 1", "race", "stock", "nascar", "speed", "drive"]
+  },
+  "racehorse": {
+    "unicode": "1F40E",
+    "unicode_alternates": [],
+    "name": "horse",
+    "shortname": ":racehorse:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "gamble", "horse", "powerful", "draft", "calvary", "cowboy", "cowgirl", "mounted", "race", "ride", "gallop", "trot", "colt", "filly", "mare", "stallion", "gelding", "yearling", "thoroughbred", "pony"],
+    "moji": "🐎"
+  },
+  "radio": {
+    "unicode": "1F4FB",
+    "unicode_alternates": [],
+    "name": "radio",
+    "shortname": ":radio:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["communication", "music", "podcast", "program"],
+    "moji": "📻"
+  },
+  "radio_button": {
+    "unicode": "1F518",
+    "unicode_alternates": [],
+    "name": "radio button",
+    "shortname": ":radio_button:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["input"],
+    "moji": "🔘"
+  },
+  "rage": {
+    "unicode": "1F621",
+    "unicode_alternates": [],
+    "name": "pouting face",
+    "shortname": ":rage:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["angry", "despise", "hate", "mad", "pout", "anger", "rage", "irate"],
+    "moji": "😡"
+  },
+  "railway_car": {
+    "unicode": "1F683",
+    "unicode_alternates": [],
+    "name": "railway car",
+    "shortname": ":railway_car:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle", "railway", "rail", "car", "coach", "train"],
+    "moji": "🚃"
+  },
+  "railway_track": {
+    "unicode": "1F6E4",
+    "unicode_alternates": [],
+    "name": "railway track",
+    "shortname": ":railway_track:",
+    "category": "travel_places",
+    "aliases": [":railroad_track:"],
+    "aliases_ascii": [],
+    "keywords": ["train", "trolley", "subway", "locomotive", "transit"]
+  },
+  "rainbow": {
+    "unicode": "1F308",
+    "unicode_alternates": [],
+    "name": "rainbow",
+    "shortname": ":rainbow:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["happy", "nature", "photo", "sky", "unicorn", "rainbow", "color", "pride", "diversity", "spectrum", "refract", "leprechaun", "gold"],
+    "moji": "🌈"
+  },
+  "raised_hand": {
+    "unicode": "270B",
+    "unicode_alternates": [],
+    "name": "raised hand",
+    "shortname": ":raised_hand:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["female", "girl", "woman"],
+    "moji": "✋"
+  },
+  "raised_hands": {
+    "unicode": "1F64C",
+    "unicode_alternates": [],
+    "name": "person raising both hands in celebration",
+    "shortname": ":raised_hands:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["gesture", "hooray", "winning", "woot", "yay", "banzai"],
+    "moji": "🙌"
+  },
+  "raising_hand": {
+    "unicode": "1F64B",
+    "unicode_alternates": [],
+    "name": "happy person raising one hand",
+    "shortname": ":raising_hand:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["female", "girl", "woman", "hand", "raise", "notice", "attention", "answer"],
+    "moji": "🙋"
+  },
+  "ram": {
+    "unicode": "1F40F",
+    "unicode_alternates": [],
+    "name": "ram",
+    "shortname": ":ram:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "sheep", "ram", "sheep", "male", "horn", "horns"],
+    "moji": "🐏"
+  },
+  "ramen": {
+    "unicode": "1F35C",
+    "unicode_alternates": [],
+    "name": "steaming bowl",
+    "shortname": ":ramen:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chipsticks", "food", "japanese", "noodle", "ramen", "noodles", "bowl", "steaming", "soup"],
+    "moji": "🍜"
+  },
+  "rat": {
+    "unicode": "1F400",
+    "unicode_alternates": [],
+    "name": "rat",
+    "shortname": ":rat:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "mouse", "rat", "rodent", "crooked", "snitch"],
+    "moji": "🐀"
+  },
+  "recycle": {
+    "unicode": "267B",
+    "unicode_alternates": ["267B-FE0F"],
+    "name": "black universal recycling symbol",
+    "shortname": ":recycle:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "environment", "garbage", "trash"],
+    "moji": "♻"
+  },
+  "red_car": {
+    "unicode": "1F697",
+    "unicode_alternates": [],
+    "name": "automobile",
+    "shortname": ":red_car:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle"],
+    "moji": "🚗"
+  },
+  "red_circle": {
+    "unicode": "1F534",
+    "unicode_alternates": [],
+    "name": "large red circle",
+    "shortname": ":red_circle:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "🔴"
+  },
+  "registered": {
+    "moji": "®",
+    "unicode": "00AE",
+    "unicode_alternates": [],
+    "name": "registered sign",
+    "shortname": ":registered:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alphabet", "circle"]
+  },
+  "relaxed": {
+    "unicode": "263A",
+    "unicode_alternates": ["263A-FE0F"],
+    "name": "white smiling face",
+    "shortname": ":relaxed:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blush", "face", "happiness", "massage", "smile"],
+    "moji": "☺"
+  },
+  "relieved": {
+    "unicode": "1F60C",
+    "unicode_alternates": [],
+    "name": "relieved face",
+    "shortname": ":relieved:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "happiness", "massage", "phew", "relaxed", "relieved", "satisfied", "phew", "relief"],
+    "moji": "😌"
+  },
+  "reminder_ribbon": {
+    "unicode": "1F397",
+    "unicode_alternates": [],
+    "name": "reminder ribbon",
+    "shortname": ":reminder_ribbon:",
+    "category": "celebration",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["awareness"]
+  },
+  "repeat": {
+    "unicode": "1F501",
+    "unicode_alternates": [],
+    "name": "clockwise rightwards and leftwards open circle arr",
+    "shortname": ":repeat:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["loop", "record"],
+    "moji": "🔁"
+  },
+  "repeat_one": {
+    "unicode": "1F502",
+    "unicode_alternates": [],
+    "name": "clockwise rightwards and leftwards open circle arr",
+    "shortname": ":repeat_one:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "loop"],
+    "moji": "🔂"
+  },
+  "restroom": {
+    "unicode": "1F6BB",
+    "unicode_alternates": [],
+    "name": "restroom",
+    "shortname": ":restroom:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "woman", "man", "unisex", "bathroom", "restroom", "sign", "shared", "toilet"],
+    "moji": "🚻"
+  },
+  "revolving_hearts": {
+    "unicode": "1F49E",
+    "unicode_alternates": [],
+    "name": "revolving hearts",
+    "shortname": ":revolving_hearts:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "like", "love", "valentines", "heart", "hearts", "revolving", "moving", "circle", "multiple", "lovers"],
+    "moji": "💞"
+  },
+  "rewind": {
+    "unicode": "23EA",
+    "unicode_alternates": [],
+    "name": "black left-pointing double triangle",
+    "shortname": ":rewind:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "play"],
+    "moji": "⏪"
+  },
+  "ribbon": {
+    "unicode": "1F380",
+    "unicode_alternates": [],
+    "name": "ribbon",
+    "shortname": ":ribbon:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bowtie", "decoration", "girl", "pink", "ribbon", "lace", "wrap", "decorate"],
+    "moji": "🎀"
+  },
+  "rice": {
+    "unicode": "1F35A",
+    "unicode_alternates": [],
+    "name": "cooked rice",
+    "shortname": ":rice:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "rice", "white", "grain", "food", "bowl"],
+    "moji": "🍚"
+  },
+  "rice_ball": {
+    "unicode": "1F359",
+    "unicode_alternates": [],
+    "name": "rice ball",
+    "shortname": ":rice_ball:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "japanese", "rice", "ball", "white", "nori", "seaweed", "japanese"],
+    "moji": "🍙"
+  },
+  "rice_cracker": {
+    "unicode": "1F358",
+    "unicode_alternates": [],
+    "name": "rice cracker",
+    "shortname": ":rice_cracker:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "japanese", "rice", "cracker", "seaweed", "food", "japanese"],
+    "moji": "🍘"
+  },
+  "rice_scene": {
+    "unicode": "1F391",
+    "unicode_alternates": [],
+    "name": "moon viewing ceremony",
+    "shortname": ":rice_scene:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["photo", "moon", "viewing", "observing", "otsukimi", "tsukimi", "rice", "scene", "festival", "autumn"],
+    "moji": "🎑"
+  },
+  "right_speaker": {
+    "unicode": "1F568",
+    "unicode_alternates": [],
+    "name": "right speaker",
+    "shortname": ":right_speaker:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sound", "listen", "hear", "noise", "volume"]
+  },
+  "right_speaker_one": {
+    "unicode": "1F569",
+    "unicode_alternates": [],
+    "name": "right speaker with one sound wave",
+    "shortname": ":right_speaker_one:",
+    "category": "objects_symbols",
+    "aliases": [":right_speaker_with_one_sound_wave:"],
+    "aliases_ascii": [],
+    "keywords": ["low", "volume"]
+  },
+  "right_speaker_three": {
+    "unicode": "1F56A",
+    "unicode_alternates": [],
+    "name": "right speaker with three sound waves",
+    "shortname": ":right_speaker_three:",
+    "category": "objects_symbols",
+    "aliases": [":right_speaker_with_three_sound_waves:"],
+    "aliases_ascii": [],
+    "keywords": ["loud", "high", "volume"]
+  },
+  "ring": {
+    "unicode": "1F48D",
+    "unicode_alternates": [],
+    "name": "ring",
+    "shortname": ":ring:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["marriage", "propose", "valentines", "wedding"],
+    "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"]
+  },
+  "rocket": {
+    "unicode": "1F680",
+    "unicode_alternates": [],
+    "name": "rocket",
+    "shortname": ":rocket:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["launch", "ship", "staffmode", "rocket", "space", "spacecraft", "astronaut", "cosmonaut"],
+    "moji": "🚀"
+  },
+  "roller_coaster": {
+    "unicode": "1F3A2",
+    "unicode_alternates": [],
+    "name": "roller coaster",
+    "shortname": ":roller_coaster:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["carnival", "fun", "photo", "play", "playground", "roller", "coaster", "amusement", "park", "fair", "ride", "entertainment"],
+    "moji": "🎢"
+  },
+  "rooster": {
+    "unicode": "1F413",
+    "unicode_alternates": [],
+    "name": "rooster",
+    "shortname": ":rooster:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "chicken", "nature", "rooster", "cockerel", "cock", "male", "cock-a-doodle-doo", "crowing"],
+    "moji": "🐓"
+  },
+  "rose": {
+    "unicode": "1F339",
+    "unicode_alternates": [],
+    "name": "rose",
+    "shortname": ":rose:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["flowers", "love", "valentines", "rose", "fragrant", "flower", "thorns", "love", "petals", "romance"],
+    "moji": "🌹"
+  },
+  "rosette": {
+    "unicode": "1F3F5",
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["flower"]
+  },
+  "rotating_light": {
+    "unicode": "1F6A8",
+    "unicode_alternates": [],
+    "name": "police cars revolving light",
+    "shortname": ":rotating_light:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["911", "ambulance", "emergency", "police", "light", "police", "emergency"],
+    "moji": "🚨"
+  },
+  "round_pushpin": {
+    "unicode": "1F4CD",
+    "unicode_alternates": [],
+    "name": "round pushpin",
+    "shortname": ":round_pushpin:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["stationery"],
+    "moji": "📍"
+  },
+  "rowboat": {
+    "unicode": "1F6A3",
+    "unicode_alternates": [],
+    "name": "rowboat",
+    "shortname": ":rowboat:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["hobby", "ship", "sports", "water", "boat", "row", "oar", "paddle"],
+    "moji": "🚣"
+  },
+  "rugby_football": {
+    "unicode": "1F3C9",
+    "unicode_alternates": [],
+    "name": "rugby football",
+    "shortname": ":rugby_football:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sports", "rugby", "football", "ball", "sport", "team", "england"],
+    "moji": "🏉"
+  },
+  "runner": {
+    "unicode": "1F3C3",
+    "unicode_alternates": [],
+    "name": "runner",
+    "shortname": ":runner:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["exercise", "man", "walking", "run", "runner", "jog", "exercise", "sprint", "race", "dash"],
+    "moji": "🏃"
+  },
+  "running_shirt_with_sash": {
+    "unicode": "1F3BD",
+    "unicode_alternates": [],
+    "name": "running shirt with sash",
+    "shortname": ":running_shirt_with_sash:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["pageant", "play", "running", "run", "shirt", "cloths", "compete", "sports"],
+    "moji": "🎽"
+  },
+  "sagittarius": {
+    "unicode": "2650",
+    "unicode_alternates": ["2650-FE0F"],
+    "name": "sagittarius",
+    "shortname": ":sagittarius:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sagittarius", "centaur", "archer", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+    "moji": "♐"
+  },
+  "sailboat": {
+    "unicode": "26F5",
+    "unicode_alternates": ["26F5-FE0F"],
+    "name": "sailboat",
+    "shortname": ":sailboat:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["ship", "transportation"],
+    "moji": "⛵"
+  },
+  "sake": {
+    "unicode": "1F376",
+    "unicode_alternates": [],
+    "name": "sake bottle and cup",
+    "shortname": ":sake:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["beverage", "drink", "drunk", "wine", "sake", "wine", "rice", "ferment", "alcohol", "japanese", "drink"],
+    "moji": "🍶"
+  },
+  "sandal": {
+    "unicode": "1F461",
+    "unicode_alternates": [],
+    "name": "womans sandal",
+    "shortname": ":sandal:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fashion", "shoes"],
+    "moji": "👡"
+  },
+  "santa": {
+    "unicode": "1F385",
+    "unicode_alternates": [],
+    "name": "father christmas",
+    "shortname": ":santa:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["christmas", "father christmas", "festival", "male", "man", "xmas", "santa", "saint nick", "jolly", "ho ho ho", "north pole", "presents", "gifts", "naughty", "nice", "sleigh", "father", "christmas", "holiday"],
+    "moji": "🎅"
+  },
+  "satellite": {
+    "unicode": "1F4E1",
+    "unicode_alternates": [],
+    "name": "satellite antenna",
+    "shortname": ":satellite:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["communication"],
+    "moji": "📡"
+  },
+  "satellite_orbital": {
+    "unicode": "1F6F0",
+    "unicode_alternates": [],
+    "name": "satellite",
+    "shortname": ":satellite_orbital:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["communication", "orbital", "space"]
+  },
+  "saxophone": {
+    "unicode": "1F3B7",
+    "unicode_alternates": [],
+    "name": "saxophone",
+    "shortname": ":saxophone:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["instrument", "music", "saxophone", "sax", "music", "instrument", "woodwind"],
+    "moji": "🎷"
+  },
+  "school": {
+    "unicode": "1F3EB",
+    "unicode_alternates": [],
+    "name": "school",
+    "shortname": ":school:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["building", "school", "university", "elementary", "middle", "high", "college", "teach", "education"],
+    "moji": "🏫"
+  },
+  "school_satchel": {
+    "unicode": "1F392",
+    "unicode_alternates": [],
+    "name": "school satchel",
+    "shortname": ":school_satchel:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bag", "education", "student", "school", "satchel", "backpack", "bag", "packing", "pack", "hike", "education", "adventure", "travel", "sightsee"],
+    "moji": "🎒"
+  },
+  "scissors": {
+    "unicode": "2702",
+    "unicode_alternates": ["2702-FE0F"],
+    "name": "black scissors",
+    "shortname": ":scissors:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cut", "stationery"],
+    "moji": "✂"
+  },
+  "scorpius": {
+    "unicode": "264F",
+    "unicode_alternates": ["264F-FE0F"],
+    "name": "scorpius",
+    "shortname": ":scorpius:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["scorpius", "scorpion", "scorpio", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+    "moji": "♏"
+  },
+  "scream": {
+    "unicode": "1F631",
+    "unicode_alternates": [],
+    "name": "face screaming in fear",
+    "shortname": ":scream:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "munch", "scream", "painting", "artist", "alien"],
+    "moji": "😱"
+  },
+  "scream_cat": {
+    "unicode": "1F640",
+    "unicode_alternates": [],
+    "name": "weary cat face",
+    "shortname": ":scream_cat:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "cats", "munch", "weary", "sleepy", "tired", "tiredness", "study", "finals", "school", "exhausted", "scream", "painting", "artist"],
+    "moji": "🙀"
+  },
+  "scroll": {
+    "unicode": "1F4DC",
+    "unicode_alternates": [],
+    "name": "scroll",
+    "shortname": ":scroll:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["documents"],
+    "moji": "📜"
+  },
+  "seat": {
+    "unicode": "1F4BA",
+    "unicode_alternates": [],
+    "name": "seat",
+    "shortname": ":seat:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sit"],
+    "moji": "💺"
+  },
+  "secret": {
+    "unicode": "3299",
+    "unicode_alternates": ["3299-FE0F"],
+    "name": "circled ideograph secret",
+    "shortname": ":secret:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["privacy"],
+    "moji": "㊙"
+  },
+  "see_no_evil": {
+    "unicode": "1F648",
+    "unicode_alternates": [],
+    "name": "see-no-evil monkey",
+    "shortname": ":see_no_evil:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "monkey", "nature", "monkey", "see", "eyes", "vision", "sight", "mizaru"],
+    "moji": "🙈"
+  },
+  "seedling": {
+    "unicode": "1F331",
+    "unicode_alternates": [],
+    "name": "seedling",
+    "shortname": ":seedling:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["grass", "lawn", "nature", "plant", "seedling", "plant", "new", "start", "grow"],
+    "moji": "🌱"
+  },
+  "seven": {
+    "moji": "7️⃣",
+    "unicode": "0037-20E3",
+    "unicode_alternates": ["0037-FE0F-20E3"],
+    "name": "digit seven",
+    "shortname": ":seven:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["7", "blue-square", "numbers", "prime"]
+  },
+  "shaved_ice": {
+    "unicode": "1F367",
+    "unicode_alternates": [],
+    "name": "shaved ice",
+    "shortname": ":shaved_ice:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["desert", "hot", "shaved", "ice", "dessert", "treat", "syrup", "flavoring"],
+    "moji": "🍧"
+  },
+  "sheep": {
+    "unicode": "1F411",
+    "unicode_alternates": [],
+    "name": "sheep",
+    "shortname": ":sheep:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "sheep", "wool", "flock", "follower", "ewe", "female", "lamb"],
+    "moji": "🐑"
+  },
+  "shell": {
+    "unicode": "1F41A",
+    "unicode_alternates": [],
+    "name": "spiral shell",
+    "shortname": ":shell:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["beach", "nature", "sea", "shell", "spiral", "beach", "sand", "crab", "nautilus"],
+    "moji": "🐚"
+  },
+  "shield": {
+    "unicode": "1F6E1",
+    "unicode_alternates": [],
+    "name": "shield",
+    "shortname": ":shield:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["interstate", "route", "sign", "highway", "interstate"]
+  },
+  "ship": {
+    "unicode": "1F6A2",
+    "unicode_alternates": [],
+    "name": "ship",
+    "shortname": ":ship:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["titanic", "transportation", "ferry", "ship", "boat"],
+    "moji": "🚢"
+  },
+  "shirt": {
+    "unicode": "1F455",
+    "unicode_alternates": [],
+    "name": "t-shirt",
+    "shortname": ":shirt:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cloth", "fashion"],
+    "moji": "👕"
+  },
+  "shopping_bags": {
+    "unicode": "1F6CD",
+    "unicode_alternates": [],
+    "name": "shopping bags",
+    "shortname": ":shopping_bags:",
+    "category": "travel_places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["purchase", "mall", "buy", "store", "shop"]
+  },
+  "shower": {
+    "unicode": "1F6BF",
+    "unicode_alternates": [],
+    "name": "shower",
+    "shortname": ":shower:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bath", "clean", "wash", "bathroom", "shower", "soap", "water", "clean", "shampoo", "lather"],
+    "moji": "🚿"
+  },
+  "signal_strength": {
+    "unicode": "1F4F6",
+    "unicode_alternates": [],
+    "name": "antenna with bars",
+    "shortname": ":signal_strength:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square"],
+    "moji": "📶"
+  },
+  "six": {
+    "moji": "6️⃣",
+    "unicode": "0036-20E3",
+    "unicode_alternates": ["0036-FE0F-20E3"],
+    "name": "digit six",
+    "shortname": ":six:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["6", "blue-square", "numbers"]
+  },
+  "six_pointed_star": {
+    "unicode": "1F52F",
+    "unicode_alternates": [],
+    "name": "six pointed star with middle dot",
+    "shortname": ":six_pointed_star:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["purple-square"],
+    "moji": "🔯"
+  },
+  "ski": {
+    "unicode": "1F3BF",
+    "unicode_alternates": [],
+    "name": "ski and ski boot",
+    "shortname": ":ski:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cold", "sports", "winter", "ski", "downhill", "cross-country", "poles", "snow", "winter", "mountain", "alpine", "powder", "slalom", "freestyle"],
+    "moji": "🎿"
+  },
+  "skull": {
+    "unicode": "1F480",
+    "unicode_alternates": [],
+    "name": "skull",
+    "shortname": ":skull:",
+    "category": "emoticons",
+    "aliases": [":skeleton:"],
+    "aliases_ascii": [],
+    "keywords": ["dead", "skeleton", "dying"],
+    "moji": "💀"
+  },
+  "sleeping": {
+    "unicode": "1F634",
+    "unicode_alternates": [],
+    "name": "sleeping face",
+    "shortname": ":sleeping:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "sleepy", "tired", "sleep", "sleepy", "sleeping", "snore"],
+    "moji": "😴"
+  },
+  "sleeping_accommodation": {
+    "unicode": "1F6CC",
+    "unicode_alternates": [],
+    "name": "sleeping accommodation",
+    "shortname": ":sleeping_accommodation:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["hotel", "motel", "rest"]
+  },
+  "sleepy": {
+    "unicode": "1F62A",
+    "unicode_alternates": [],
+    "name": "sleepy face",
+    "shortname": ":sleepy:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "rest", "tired", "sleepy", "tired", "exhausted"],
+    "moji": "😪"
+  },
+  "slight_frown": {
+    "unicode": "1F641",
+    "unicode_alternates": [],
+    "name": "slightly frowning face",
+    "shortname": ":slight_frown:",
+    "category": "people",
+    "aliases": [":slightly_frowning_face:"],
+    "aliases_ascii": [],
+    "keywords": ["slight", "frown", "unhappy", "disappointed"]
+  },
+  "slight_smile": {
+    "unicode": "1F642",
+    "unicode_alternates": [],
+    "name": "slightly smiling face",
+    "shortname": ":slight_smile:",
+    "category": "people",
+    "aliases": [":slightly_smiling_face:"],
+    "aliases_ascii": [],
+    "keywords": ["slight", "smile", "happy"]
+  },
+  "slot_machine": {
+    "unicode": "1F3B0",
+    "unicode_alternates": [],
+    "name": "slot machine",
+    "shortname": ":slot_machine:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bet", "gamble", "vegas", "slot", "machine", "gamble", "one-armed bandit", "slots", "luck"],
+    "moji": "🎰"
+  },
+  "small_blue_diamond": {
+    "unicode": "1F539",
+    "unicode_alternates": [],
+    "name": "small blue diamond",
+    "shortname": ":small_blue_diamond:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "🔹"
+  },
+  "small_orange_diamond": {
+    "unicode": "1F538",
+    "unicode_alternates": [],
+    "name": "small orange diamond",
+    "shortname": ":small_orange_diamond:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "🔸"
+  },
+  "small_red_triangle": {
+    "unicode": "1F53A",
+    "unicode_alternates": [],
+    "name": "up-pointing red triangle",
+    "shortname": ":small_red_triangle:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "🔺"
+  },
+  "small_red_triangle_down": {
+    "unicode": "1F53B",
+    "unicode_alternates": [],
+    "name": "down-pointing red triangle",
+    "shortname": ":small_red_triangle_down:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "🔻"
+  },
+  "smile": {
+    "unicode": "1F604",
+    "unicode_alternates": [],
+    "name": "smiling face with open mouth and smiling eyes",
+    "shortname": ":smile:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [":)", ":-)", "=]", "=)", ":]"],
+    "keywords": ["face", "funny", "haha", "happy", "joy", "laugh", "smile", "smiley", "smiling"],
+    "moji": "😄"
+  },
+  "smile_cat": {
+    "unicode": "1F638",
+    "unicode_alternates": [],
+    "name": "grinning cat face with smiling eyes",
+    "shortname": ":smile_cat:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "cats", "cat", "smile", "grin", "grinning"],
+    "moji": "😸"
+  },
+  "smiley": {
+    "unicode": "1F603",
+    "unicode_alternates": [],
+    "name": "smiling face with open mouth",
+    "shortname": ":smiley:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [":D", ":-D", "=D"],
+    "keywords": ["face", "haha", "happy", "joy", "smiling", "smile", "smiley"],
+    "moji": "😃"
+  },
+  "smiley_cat": {
+    "unicode": "1F63A",
+    "unicode_alternates": [],
+    "name": "smiling cat face with open mouth",
+    "shortname": ":smiley_cat:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "cats", "happy", "smile", "smiley", "cat", "happy"],
+    "moji": "😺"
+  },
+  "smiling_imp": {
+    "unicode": "1F608",
+    "unicode_alternates": [],
+    "name": "smiling face with horns",
+    "shortname": ":smiling_imp:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["devil", "horns", "horns", "devil", "impish", "trouble"],
+    "moji": "😈"
+  },
+  "smirk": {
+    "unicode": "1F60F",
+    "unicode_alternates": [],
+    "name": "smirking face",
+    "shortname": ":smirk:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["mean", "prank", "smile", "smug", "smirking", "smirk", "smug", "smile", "half-smile", "conceited"],
+    "moji": "😏"
+  },
+  "smirk_cat": {
+    "unicode": "1F63C",
+    "unicode_alternates": [],
+    "name": "cat face with wry smile",
+    "shortname": ":smirk_cat:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "cats", "smirk", "smirking", "wry", "confident", "confidence"],
+    "moji": "😼"
+  },
+  "smoking": {
+    "unicode": "1F6AC",
+    "unicode_alternates": [],
+    "name": "smoking symbol",
+    "shortname": ":smoking:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cigarette", "kills", "tobacco", "smoking", "cigarette", "smoke", "cancer", "lungs", "inhale", "tar", "nicotine"],
+    "moji": "🚬"
+  },
+  "snail": {
+    "unicode": "1F40C",
+    "unicode_alternates": [],
+    "name": "snail",
+    "shortname": ":snail:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "shell", "slow", "snail", "slow", "escargot", "french", "appetizer"],
+    "moji": "🐌"
+  },
+  "snake": {
+    "unicode": "1F40D",
+    "unicode_alternates": [],
+    "name": "snake",
+    "shortname": ":snake:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "evil"],
+    "moji": "🐍"
+  },
+  "snowboarder": {
+    "unicode": "1F3C2",
+    "unicode_alternates": [],
+    "name": "snowboarder",
+    "shortname": ":snowboarder:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sports", "winter", "snow", "boarding", "sports", "freestyle", "halfpipe", "board", "mountain", "alpine", "winter"],
+    "moji": "🏂"
+  },
+  "snowflake": {
+    "unicode": "2744",
+    "unicode_alternates": ["2744-FE0F"],
+    "name": "snowflake",
+    "shortname": ":snowflake:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["christmas", "cold", "season", "weather", "winter", "xmas", "snowflake", "snow", "frozen", "droplet", "ice", "crystal", "cold", "chilly", "winter", "unique", "special", "below zero", "elsa"],
+    "moji": "❄"
+  },
+  "snowman": {
+    "unicode": "26C4",
+    "unicode_alternates": ["26C4-FE0F"],
+    "name": "snowman without snow",
+    "shortname": ":snowman:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["christmas", "cold", "season", "weather", "winter", "xmas"],
+    "moji": "⛄"
+  },
+  "sob": {
+    "unicode": "1F62D",
+    "unicode_alternates": [],
+    "name": "loudly crying face",
+    "shortname": ":sob:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cry", "face", "sad", "tears", "upset", "cry", "sob", "tears", "sad", "melancholy", "morn", "somber", "hurt"],
+    "moji": "😭"
+  },
+  "soccer": {
+    "unicode": "26BD",
+    "unicode_alternates": ["26BD-FE0F"],
+    "name": "soccer ball",
+    "shortname": ":soccer:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["balls", "fifa", "football", "sports", "european", "football"],
+    "moji": "⚽"
+  },
+  "soon": {
+    "unicode": "1F51C",
+    "unicode_alternates": [],
+    "name": "soon with rightwards arrow above",
+    "shortname": ":soon:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arrow", "words"],
+    "moji": "🔜"
+  },
+  "sos": {
+    "unicode": "1F198",
+    "unicode_alternates": [],
+    "name": "squared sos",
+    "shortname": ":sos:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["emergency", "help", "red-square", "words"],
+    "moji": "🆘"
+  },
+  "sound": {
+    "unicode": "1F509",
+    "unicode_alternates": [],
+    "name": "speaker with one sound wave",
+    "shortname": ":sound:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["speaker", "volume"],
+    "moji": "🔉"
+  },
+  "space_invader": {
+    "unicode": "1F47E",
+    "unicode_alternates": [],
+    "name": "alien monster",
+    "shortname": ":space_invader:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arcade", "game"],
+    "moji": "👾"
+  },
+  "spades": {
+    "unicode": "2660",
+    "unicode_alternates": ["2660-FE0F"],
+    "name": "black spade suit",
+    "shortname": ":spades:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cards", "poker"],
+    "moji": "♠"
+  },
+  "spaghetti": {
+    "unicode": "1F35D",
+    "unicode_alternates": [],
+    "name": "spaghetti",
+    "shortname": ":spaghetti:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "italian", "noodle", "spaghetti", "noodles", "tomato", "sauce", "italian"],
+    "moji": "🍝"
+  },
+  "sparkle": {
+    "unicode": "2747",
+    "unicode_alternates": ["2747-FE0F"],
+    "name": "sparkle",
+    "shortname": ":sparkle:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["green-square", "stars"],
+    "moji": "❇"
+  },
+  "sparkler": {
+    "unicode": "1F387",
+    "unicode_alternates": [],
+    "name": "firework sparkler",
+    "shortname": ":sparkler:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["night", "shine", "stars"],
+    "moji": "🎇"
+  },
+  "sparkles": {
+    "unicode": "2728",
+    "unicode_alternates": [],
+    "name": "sparkles",
+    "shortname": ":sparkles:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cool", "shine", "shiny", "stars"],
+    "moji": "✨"
+  },
+  "sparkling_heart": {
+    "unicode": "1F496",
+    "unicode_alternates": [],
+    "name": "sparkling heart",
+    "shortname": ":sparkling_heart:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "like", "love", "valentines"],
+    "moji": "💖"
+  },
+  "speak_no_evil": {
+    "unicode": "1F64A",
+    "unicode_alternates": [],
+    "name": "speak-no-evil monkey",
+    "shortname": ":speak_no_evil:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "monkey", "monkey", "mouth", "talk", "say", "words", "verbal", "verbalize", "oral", "iwazaru"],
+    "moji": "🙊"
+  },
+  "speaker": {
+    "unicode": "1F508",
+    "unicode_alternates": [],
+    "name": "speaker",
+    "shortname": ":speaker:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sound", "listen", "hear", "noise"]
+  },
+  "speaking_head": {
+    "unicode": "1F5E3",
+    "unicode_alternates": [],
+    "name": "speaking head in silhouette",
+    "shortname": ":speaking_head:",
+    "category": "objects_symbols",
+    "aliases": [":speaking_head_in_silhouette:"],
+    "aliases_ascii": [],
+    "keywords": ["talk"]
+  },
+  "speech_balloon": {
+    "unicode": "1F4AC",
+    "unicode_alternates": [],
+    "name": "speech balloon",
+    "shortname": ":speech_balloon:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bubble", "words", "speech", "balloon", "talk", "conversation", "communication", "comic", "dialogue"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["ship", "transportation", "vehicle", "motor", "speed", "ski", "power", "boat"],
+    "moji": "🚤"
+  },
+  "spider": {
+    "unicode": "1F577",
+    "unicode_alternates": [],
+    "name": "spider",
+    "shortname": ":spider:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["arachnid", "eight-legged"]
+  },
+  "spider_web": {
+    "unicode": "1F578",
+    "unicode_alternates": [],
+    "name": "spider web",
+    "shortname": ":spider_web:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cobweb"]
+  },
+  "spy": {
+    "unicode": "1F575",
+    "unicode_alternates": [],
+    "name": "sleuth or spy",
+    "shortname": ":spy:",
+    "category": "people",
+    "aliases": [":sleuth_or_spy:"],
+    "aliases_ascii": [],
+    "keywords": ["pi", "undercover", "investigator"]
+  },
+  "stadium": {
+    "unicode": "1F3DF",
+    "unicode_alternates": [],
+    "name": "stadium",
+    "shortname": ":stadium:",
+    "category": "travel_places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sport", "event", "concert", "convention", "game"]
+  },
+  "star": {
+    "unicode": "2B50",
+    "unicode_alternates": ["2B50-FE0F"],
+    "name": "white medium star",
+    "shortname": ":star:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["night", "yellow"],
+    "moji": "⭐"
+  },
+  "star2": {
+    "unicode": "1F31F",
+    "unicode_alternates": [],
+    "name": "glowing star",
+    "shortname": ":star2:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["night", "sparkle", "glow", "glowing", "star", "five", "points", "classic"],
+    "moji": "🌟"
+  },
+  "stars": {
+    "unicode": "1F320",
+    "unicode_alternates": [],
+    "name": "shooting star",
+    "shortname": ":stars:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["night", "photo", "shooting", "shoot", "star", "sky", "night", "comet", "meteoroid"],
+    "moji": "🌠"
+  },
+  "station": {
+    "unicode": "1F689",
+    "unicode_alternates": [],
+    "name": "station",
+    "shortname": ":station:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["public", "transportation", "vehicle", "station", "train", "subway"],
+    "moji": "🚉"
+  },
+  "statue_of_liberty": {
+    "unicode": "1F5FD",
+    "unicode_alternates": [],
+    "name": "statue of liberty",
+    "shortname": ":statue_of_liberty:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["american", "newyork"],
+    "moji": "🗽"
+  },
+  "steam_locomotive": {
+    "unicode": "1F682",
+    "unicode_alternates": [],
+    "name": "steam locomotive",
+    "shortname": ":steam_locomotive:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["train", "transportation", "vehicle", "locomotive", "steam", "train", "engine"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "meat", "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"]
+  },
+  "straight_ruler": {
+    "unicode": "1F4CF",
+    "unicode_alternates": [],
+    "name": "straight ruler",
+    "shortname": ":straight_ruler:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["stationery"],
+    "moji": "📏"
+  },
+  "strawberry": {
+    "unicode": "1F353",
+    "unicode_alternates": [],
+    "name": "strawberry",
+    "shortname": ":strawberry:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "fruit", "nature", "strawberry", "short", "cake", "berry"],
+    "moji": "🍓"
+  },
+  "stuck_out_tongue": {
+    "unicode": "1F61B",
+    "unicode_alternates": [],
+    "name": "face with stuck-out tongue",
+    "shortname": ":stuck_out_tongue:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [":P", ":-P", "=P", ":-p", ":p", "=p", ":-Þ", ":Þ", ":þ", ":-þ", ":-b", ":b", "d:"],
+    "keywords": ["childish", "face", "mischievous", "playful", "prank", "tongue", "silly", "playful", "cheeky"],
+    "moji": "😛"
+  },
+  "stuck_out_tongue_closed_eyes": {
+    "unicode": "1F61D",
+    "unicode_alternates": [],
+    "name": "face with stuck-out tongue and tightly-closed eyes",
+    "shortname": ":stuck_out_tongue_closed_eyes:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "mischievous", "playful", "prank", "tongue", "kidding", "silly", "playful", "ecstatic"],
+    "moji": "😝"
+  },
+  "stuck_out_tongue_winking_eye": {
+    "unicode": "1F61C",
+    "unicode_alternates": [],
+    "name": "face with stuck-out tongue and winking eye",
+    "shortname": ":stuck_out_tongue_winking_eye:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [">:P", "X-P", "x-p"],
+    "keywords": ["childish", "face", "mischievous", "playful", "prank", "tongue", "wink", "winking", "kidding", "silly", "playful", "crazy"],
+    "moji": "😜"
+  },
+  "sun_with_face": {
+    "unicode": "1F31E",
+    "unicode_alternates": [],
+    "name": "sun with face",
+    "shortname": ":sun_with_face:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["morning", "sun", "anthropomorphic", "face", "sky"],
+    "moji": "🌞"
+  },
+  "sunflower": {
+    "unicode": "1F33B",
+    "unicode_alternates": [],
+    "name": "sunflower",
+    "shortname": ":sunflower:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "plant", "sunflower", "sun", "flower", "seeds", "yellow"],
+    "moji": "🌻"
+  },
+  "sunglasses": {
+    "unicode": "1F60E",
+    "unicode_alternates": [],
+    "name": "smiling face with sunglasses",
+    "shortname": ":sunglasses:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": ["B-)", "B)", "8)", "8-)", "B-D", "8-D"],
+    "keywords": ["cool", "face", "smiling", "sunglasses", "sun", "glasses", "sunny", "cool", "smooth"],
+    "moji": "😎"
+  },
+  "sunny": {
+    "unicode": "2600",
+    "unicode_alternates": ["2600-FE0F"],
+    "name": "black sun with rays",
+    "shortname": ":sunny:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["brightness", "weather"]
+  },
+  "sunrise": {
+    "unicode": "1F305",
+    "unicode_alternates": [],
+    "name": "sunrise",
+    "shortname": ":sunrise:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["morning", "photo", "vacation", "view", "sunrise", "sun", "morning", "color", "sky"],
+    "moji": "🌅"
+  },
+  "sunrise_over_mountains": {
+    "unicode": "1F304",
+    "unicode_alternates": [],
+    "name": "sunrise over mountains",
+    "shortname": ":sunrise_over_mountains:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["photo", "vacation", "view", "sunrise", "sun", "morning", "mountain", "rural", "color", "sky"],
+    "moji": "🌄"
+  },
+  "surfer": {
+    "unicode": "1F3C4",
+    "unicode_alternates": [],
+    "name": "surfer",
+    "shortname": ":surfer:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["ocean", "sea", "sports", "surfer", "surf", "wave", "ocean", "ride", "swell"],
+    "moji": "🏄"
+  },
+  "sushi": {
+    "unicode": "1F363",
+    "unicode_alternates": [],
+    "name": "sushi",
+    "shortname": ":sushi:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "japanese", "sushi", "fish", "raw", "nigiri", "japanese"],
+    "moji": "🍣"
+  },
+  "suspension_railway": {
+    "unicode": "1F69F",
+    "unicode_alternates": [],
+    "name": "suspension railway",
+    "shortname": ":suspension_railway:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle", "suspension", "railway", "rail", "train", "transportation"],
+    "moji": "🚟"
+  },
+  "sweat": {
+    "unicode": "1F613",
+    "unicode_alternates": [],
+    "name": "face with cold sweat",
+    "shortname": ":sweat:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": ["':(", "':-(", "'=("],
+    "keywords": ["cold", "sweat", "sick", "anxious", "worried", "clammy", "diaphoresis", "face", "hot"],
+    "moji": "😓"
+  },
+  "sweat_drops": {
+    "unicode": "1F4A6",
+    "unicode_alternates": [],
+    "name": "splashing sweat symbol",
+    "shortname": ":sweat_drops:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["water"],
+    "moji": "💦"
+  },
+  "sweat_smile": {
+    "unicode": "1F605",
+    "unicode_alternates": [],
+    "name": "smiling face with open mouth and cold sweat",
+    "shortname": ":sweat_smile:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": ["':)", "':-)", "'=)", "':D", "':-D", "'=D"],
+    "keywords": ["face", "happy", "hot", "smiling", "cold", "sweat", "perspiration"],
+    "moji": "😅"
+  },
+  "sweet_potato": {
+    "unicode": "1F360",
+    "unicode_alternates": [],
+    "name": "roasted sweet potato",
+    "shortname": ":sweet_potato:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "nature", "sweet", "potato", "potassium", "roasted", "roast"],
+    "moji": "🍠"
+  },
+  "swimmer": {
+    "unicode": "1F3CA",
+    "unicode_alternates": [],
+    "name": "swimmer",
+    "shortname": ":swimmer:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sports", "swimmer", "swim", "water", "pool", "laps", "freestyle", "butterfly", "breaststroke", "backstroke"],
+    "moji": "🏊"
+  },
+  "symbols": {
+    "unicode": "1F523",
+    "unicode_alternates": [],
+    "name": "input symbol for symbols",
+    "shortname": ":symbols:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square"],
+    "moji": "🔣"
+  },
+  "syringe": {
+    "unicode": "1F489",
+    "unicode_alternates": [],
+    "name": "syringe",
+    "shortname": ":syringe:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blood", "drugs", "health", "hospital", "medicine", "needle"],
+    "moji": "💉"
+  },
+  "tada": {
+    "unicode": "1F389",
+    "unicode_alternates": [],
+    "name": "party popper",
+    "shortname": ":tada:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["contulations", "party", "party", "popper", "tada", "celebration", "victory", "announcement", "climax", "congratulations"],
+    "moji": "🎉"
+  },
+  "tanabata_tree": {
+    "unicode": "1F38B",
+    "unicode_alternates": [],
+    "name": "tanabata tree",
+    "shortname": ":tanabata_tree:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "plant", "tanabata", "tree", "festival", "star", "wish", "holiday"],
+    "moji": "🎋"
+  },
+  "tangerine": {
+    "unicode": "1F34A",
+    "unicode_alternates": [],
+    "name": "tangerine",
+    "shortname": ":tangerine:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "fruit", "nature", "tangerine", "citrus", "orange"],
+    "moji": "🍊"
+  },
+  "taurus": {
+    "unicode": "2649",
+    "unicode_alternates": ["2649-FE0F"],
+    "name": "taurus",
+    "shortname": ":taurus:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["purple-square", "sign", "taurus", "bull", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "zodiac", "horoscope"],
+    "moji": "♉"
+  },
+  "taxi": {
+    "unicode": "1F695",
+    "unicode_alternates": [],
+    "name": "taxi",
+    "shortname": ":taxi:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cars", "transportation", "uber", "vehicle", "taxi", "car", "automobile", "city", "transport", "service"],
+    "moji": "🚕"
+  },
+  "tea": {
+    "unicode": "1F375",
+    "unicode_alternates": [],
+    "name": "teacup without handle",
+    "shortname": ":tea:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bowl", "breakfast", "british", "drink", "green", "tea", "leaf", "drink", "teacup", "hot", "beverage"],
+    "moji": "🍵"
+  },
+  "telephone": {
+    "unicode": "260E",
+    "unicode_alternates": ["260E-FE0F"],
+    "name": "black telephone",
+    "shortname": ":telephone:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["communication", "dial", "technology"],
+    "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": [],
+    "name": "telephone receiver",
+    "shortname": ":telephone_receiver:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["communication", "dial", "technology"],
+    "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": [],
+    "name": "telescope",
+    "shortname": ":telescope:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["space", "stars"],
+    "moji": "🔭"
+  },
+  "tennis": {
+    "unicode": "1F3BE",
+    "unicode_alternates": [],
+    "name": "tennis racquet and ball",
+    "shortname": ":tennis:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["balls", "green", "sports", "tennis", "racket", "racquet", "ball", "game", "net", "court", "love"],
+    "moji": "🎾"
+  },
+  "tent": {
+    "unicode": "26FA",
+    "unicode_alternates": ["26FA-FE0F"],
+    "name": "tent",
+    "shortname": ":tent:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["camp", "outdoors", "photo"],
+    "moji": "⛺"
+  },
+  "thermometer": {
+    "unicode": "1F321",
+    "unicode_alternates": [],
+    "name": "thermometer",
+    "shortname": ":thermometer:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["temperature"]
+  },
+  "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"],
+    "moji": "💭"
+  },
+  "thought_left": {
+    "unicode": "1F5EC",
+    "unicode_alternates": [],
+    "name": "left thought bubble",
+    "shortname": ":thought_left:",
+    "category": "objects_symbols",
+    "aliases": [":left_thought_bubble:"],
+    "aliases_ascii": [],
+    "keywords": ["balloon", "cloud", "comic", "think", "day dream", "wonder"]
+  },
+  "thought_right": {
+    "unicode": "1F5ED",
+    "unicode_alternates": [],
+    "name": "right thought bubble",
+    "shortname": ":thought_right:",
+    "category": "objects_symbols",
+    "aliases": [":right_thought_bubble:"],
+    "aliases_ascii": [],
+    "keywords": ["balloon", "cloud", "comic", "think", "day dream", "wonder"]
+  },
+  "three": {
+    "moji": "3️⃣",
+    "unicode": "0033-20E3",
+    "unicode_alternates": ["0033-FE0F-20E3"],
+    "name": "digit three",
+    "shortname": ":three:",
+    "category": "other",
+    "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"]
+  },
+  "thumbsdown": {
+    "unicode": "1F44E",
+    "unicode_alternates": [],
+    "name": "thumbs down sign",
+    "shortname": ":thumbsdown:",
+    "category": "emoticons",
+    "aliases": [":-1:"],
+    "aliases_ascii": [],
+    "keywords": ["hand", "no"],
+    "moji": "👎"
+  },
+  "thumbsup": {
+    "unicode": "1F44D",
+    "unicode_alternates": [],
+    "name": "thumbs up sign",
+    "shortname": ":thumbsup:",
+    "category": "emoticons",
+    "aliases": [":+1:"],
+    "aliases_ascii": [],
+    "keywords": ["cool", "hand", "like", "yes"],
+    "moji": "👍"
+  },
+  "ticket": {
+    "unicode": "1F3AB",
+    "unicode_alternates": [],
+    "name": "ticket",
+    "shortname": ":ticket:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["concert", "event", "pass", "ticket", "show", "entertainment", "stub", "admission", "proof", "purchase"],
+    "moji": "🎫"
+  },
+  "tickets": {
+    "unicode": "1F39F",
+    "unicode_alternates": [],
+    "name": "admission tickets",
+    "shortname": ":tickets:",
+    "category": "activity",
+    "aliases": [":admission_tickets:"],
+    "aliases_ascii": [],
+    "keywords": ["concert", "event", "pass", "show", "entertainment", "stub", "proof", "purchase"]
+  },
+  "tiger": {
+    "unicode": "1F42F",
+    "unicode_alternates": [],
+    "name": "tiger face",
+    "shortname": ":tiger:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal"],
+    "moji": "🐯"
+  },
+  "tiger2": {
+    "unicode": "1F405",
+    "unicode_alternates": [],
+    "name": "tiger",
+    "shortname": ":tiger2:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "tiger", "cat", "striped", "tony", "tigger", "hobs"],
+    "moji": "🐅"
+  },
+  "tired_face": {
+    "unicode": "1F62B",
+    "unicode_alternates": [],
+    "name": "tired face",
+    "shortname": ":tired_face:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "frustrated", "sick", "upset", "whine", "exhausted", "sleepy", "tired"],
+    "moji": "😫"
+  },
+  "toilet": {
+    "unicode": "1F6BD",
+    "unicode_alternates": [],
+    "name": "toilet",
+    "shortname": ":toilet:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["restroom", "wc", "toilet", "bathroom", "throne", "porcelain", "waste", "flush", "plumbing"],
+    "moji": "🚽"
+  },
+  "tokyo_tower": {
+    "unicode": "1F5FC",
+    "unicode_alternates": [],
+    "name": "tokyo tower",
+    "shortname": ":tokyo_tower:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["japan", "photo"],
+    "moji": "🗼"
+  },
+  "tomato": {
+    "unicode": "1F345",
+    "unicode_alternates": [],
+    "name": "tomato",
+    "shortname": ":tomato:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "fruit", "nature", "vegetable", "tomato", "fruit", "sauce", "italian"],
+    "moji": "🍅"
+  },
+  "tongue": {
+    "unicode": "1F445",
+    "unicode_alternates": [],
+    "name": "tongue",
+    "shortname": ":tongue:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["mouth", "playful", "tongue", "mouth", "taste", "buds", "food", "silly", "playful", "tease", "kiss", "french kiss", "lick", "tasty", "playfulness", "silliness", "intimacy"],
+    "moji": "👅"
+  },
+  "tools": {
+    "unicode": "1F6E0",
+    "unicode_alternates": [],
+    "name": "hammer and wrench",
+    "shortname": ":tools:",
+    "category": "objects_symbols",
+    "aliases": [":hammer_and_wrench:"],
+    "aliases_ascii": [],
+    "keywords": ["tools"]
+  },
+  "top": {
+    "unicode": "1F51D",
+    "unicode_alternates": [],
+    "name": "top with upwards arrow above",
+    "shortname": ":top:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "words"],
+    "moji": "🔝"
+  },
+  "tophat": {
+    "unicode": "1F3A9",
+    "unicode_alternates": [],
+    "name": "top hat",
+    "shortname": ":tophat:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["classy", "gentleman", "magic", "top", "hat", "cap", "beaver", "high", "tall", "stove", "pipe", "chimney", "topper", "london", "period piece", "magic", "magician"],
+    "moji": "🎩"
+  },
+  "trackball": {
+    "unicode": "1F5B2",
+    "unicode_alternates": [],
+    "name": "trackball",
+    "shortname": ":trackball:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["input", "device", "gadget"]
+  },
+  "tractor": {
+    "unicode": "1F69C",
+    "unicode_alternates": [],
+    "name": "tractor",
+    "shortname": ":tractor:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["agriculture", "car", "farming", "vehicle", "tractor", "farm", "construction", "machine", "digger"],
+    "moji": "🚜"
+  },
+  "traffic_light": {
+    "unicode": "1F6A5",
+    "unicode_alternates": [],
+    "name": "horizontal traffic light",
+    "shortname": ":traffic_light:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["traffic", "transportation", "traffic", "light", "stop", "go", "yield", "horizontal"],
+    "moji": "🚥"
+  },
+  "train": {
+    "unicode": "1F68B",
+    "unicode_alternates": [],
+    "name": "Tram Car",
+    "shortname": ":train:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["tram", "rail"]
+  },
+  "train2": {
+    "unicode": "1F686",
+    "unicode_alternates": [],
+    "name": "train",
+    "shortname": ":train2:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle", "train", "locomotive", "rail"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "vehicle", "tram", "transportation", "transport"],
+    "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",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["triangle", "triangular", "flag", "golf", "post", "flagpole"],
+    "moji": "🚩"
+  },
+  "triangular_ruler": {
+    "unicode": "1F4D0",
+    "unicode_alternates": [],
+    "name": "triangular ruler",
+    "shortname": ":triangular_ruler:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["architect", "math", "sketch", "stationery"],
+    "moji": "📐"
+  },
+  "trident": {
+    "unicode": "1F531",
+    "unicode_alternates": [],
+    "name": "trident emblem",
+    "shortname": ":trident:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["spear", "weapon"],
+    "moji": "🔱"
+  },
+  "triumph": {
+    "unicode": "1F624",
+    "unicode_alternates": [],
+    "name": "face with look of triumph",
+    "shortname": ":triumph:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "gas", "phew", "triumph", "steam", "breath"],
+    "moji": "😤"
+  },
+  "trolleybus": {
+    "unicode": "1F68E",
+    "unicode_alternates": [],
+    "name": "trolleybus",
+    "shortname": ":trolleybus:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bart", "transportation", "vehicle", "trolley", "bus", "city", "transport", "transportation"],
+    "moji": "🚎"
+  },
+  "trophy": {
+    "unicode": "1F3C6",
+    "unicode_alternates": [],
+    "name": "trophy",
+    "shortname": ":trophy:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["award", "ceremony", "contest", "ftw", "place", "win", "trophy", "first", "show", "place", "win", "reward", "achievement", "medal"],
+    "moji": "🏆"
+  },
+  "tropical_drink": {
+    "unicode": "1F379",
+    "unicode_alternates": [],
+    "name": "tropical drink",
+    "shortname": ":tropical_drink:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["beverage", "tropical", "drink", "mixed", "pineapple", "coconut", "pina", "fruit", "umbrella"],
+    "moji": "🍹"
+  },
+  "tropical_fish": {
+    "unicode": "1F420",
+    "unicode_alternates": [],
+    "name": "tropical fish",
+    "shortname": ":tropical_fish:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "swim"],
+    "moji": "🐠"
+  },
+  "truck": {
+    "unicode": "1F69A",
+    "unicode_alternates": [],
+    "name": "delivery truck",
+    "shortname": ":truck:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["cars", "transportation", "truck", "delivery", "package"],
+    "moji": "🚚"
+  },
+  "trumpet": {
+    "unicode": "1F3BA",
+    "unicode_alternates": [],
+    "name": "trumpet",
+    "shortname": ":trumpet:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["brass", "music", "trumpet", "brass", "music", "instrument"],
+    "moji": "🎺"
+  },
+  "tulip": {
+    "unicode": "1F337",
+    "unicode_alternates": [],
+    "name": "tulip",
+    "shortname": ":tulip:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["flowers", "nature", "plant", "tulip", "flower", "bulb", "spring", "easter"],
+    "moji": "🌷"
+  },
+  "turned_ok_hand": {
+    "unicode": "1F58F",
+    "unicode_alternates": [],
+    "name": "turned ok hand sign",
+    "shortname": ":turned_ok_hand:",
+    "category": "people",
+    "aliases": [":turned_ok_hand_sign:"],
+    "aliases_ascii": [],
+    "keywords": ["perfect", "okay"]
+  },
+  "turtle": {
+    "unicode": "1F422",
+    "unicode_alternates": [],
+    "name": "turtle",
+    "shortname": ":turtle:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "slow", "turtle", "shell", "tortoise", "chelonian", "reptile", "slow", "snap", "steady"],
+    "moji": "🐢"
+  },
+  "twisted_rightwards_arrows": {
+    "unicode": "1F500",
+    "unicode_alternates": [],
+    "name": "twisted rightwards arrows",
+    "shortname": ":twisted_rightwards_arrows:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square"],
+    "moji": "🔀"
+  },
+  "two": {
+    "moji": "2️⃣",
+    "unicode": "0032-20E3",
+    "unicode_alternates": ["0032-FE0F-20E3"],
+    "name": "digit two",
+    "shortname": ":two:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["2", "blue-square", "numbers", "prime"]
+  },
+  "two_hearts": {
+    "unicode": "1F495",
+    "unicode_alternates": [],
+    "name": "two hearts",
+    "shortname": ":two_hearts:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "like", "love", "valentines", "heart", "hearts", "two", "love", "emotion"],
+    "moji": "💕"
+  },
+  "two_men_holding_hands": {
+    "unicode": "1F46C",
+    "unicode_alternates": [],
+    "name": "two men holding hands",
+    "shortname": ":two_men_holding_hands:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bromance", "couple", "friends", "like", "love", "men", "gay", "homosexual", "friends", "hands", "holding", "team", "unity"],
+    "moji": "👬"
+  },
+  "two_women_holding_hands": {
+    "unicode": "1F46D",
+    "unicode_alternates": [],
+    "name": "two women holding hands",
+    "shortname": ":two_women_holding_hands:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["couple", "female", "friends", "like", "love", "women", "hands", "girlfriends", "friends", "sisters", "mother", "daughter", "gay", "homosexual", "couple", "unity"],
+    "moji": "👭"
+  },
+  "u5272": {
+    "unicode": "1F239",
+    "unicode_alternates": [],
+    "name": "squared cjk unified ideograph-5272",
+    "shortname": ":u5272:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "cut", "divide", "kanji", "pink"],
+    "moji": "🈹"
+  },
+  "u5408": {
+    "unicode": "1F234",
+    "unicode_alternates": [],
+    "name": "squared cjk unified ideograph-5408",
+    "shortname": ":u5408:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "japanese", "join", "kanji"],
+    "moji": "🈴"
+  },
+  "u55b6": {
+    "unicode": "1F23A",
+    "unicode_alternates": [],
+    "name": "squared cjk unified ideograph-55b6",
+    "shortname": ":u55b6:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["japanese", "opening hours"],
+    "moji": "🈺"
+  },
+  "u6307": {
+    "unicode": "1F22F",
+    "unicode_alternates": ["1F22F-FE0F"],
+    "name": "squared cjk unified ideograph-6307",
+    "shortname": ":u6307:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "green-square", "kanji", "point"],
+    "moji": "🈯"
+  },
+  "u6708": {
+    "unicode": "1F237",
+    "unicode_alternates": [],
+    "name": "squared cjk unified ideograph-6708",
+    "shortname": ":u6708:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "japanese", "kanji", "moon", "orange-square"],
+    "moji": "🈷"
+  },
+  "u6709": {
+    "unicode": "1F236",
+    "unicode_alternates": [],
+    "name": "squared cjk unified ideograph-6709",
+    "shortname": ":u6709:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "have", "kanji", "orange-square"],
+    "moji": "🈶"
+  },
+  "u6e80": {
+    "unicode": "1F235",
+    "unicode_alternates": [],
+    "name": "squared cjk unified ideograph-6e80",
+    "shortname": ":u6e80:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "full", "japanese", "kanji", "red-square"],
+    "moji": "🈵"
+  },
+  "u7121": {
+    "unicode": "1F21A",
+    "unicode_alternates": ["1F21A-FE0F"],
+    "name": "squared cjk unified ideograph-7121",
+    "shortname": ":u7121:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "japanese", "kanji", "no", "nothing", "orange-square"],
+    "moji": "🈚"
+  },
+  "u7533": {
+    "unicode": "1F238",
+    "unicode_alternates": [],
+    "name": "squared cjk unified ideograph-7533",
+    "shortname": ":u7533:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "japanese", "kanji"],
+    "moji": "🈸"
+  },
+  "u7981": {
+    "unicode": "1F232",
+    "unicode_alternates": [],
+    "name": "squared cjk unified ideograph-7981",
+    "shortname": ":u7981:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "forbidden", "japanese", "kanji", "limit", "restricted"],
+    "moji": "🈲"
+  },
+  "u7a7a": {
+    "unicode": "1F233",
+    "unicode_alternates": [],
+    "name": "squared cjk unified ideograph-7a7a",
+    "shortname": ":u7a7a:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["chinese", "empty", "japanese", "kanji"],
+    "moji": "🈳"
+  },
+  "umbrella": {
+    "unicode": "2614",
+    "unicode_alternates": ["2614-FE0F"],
+    "name": "umbrella with rain drops",
+    "shortname": ":umbrella:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["rain", "weather"],
+    "moji": "☔"
+  },
+  "unamused": {
+    "unicode": "1F612",
+    "unicode_alternates": [],
+    "name": "unamused face",
+    "shortname": ":unamused:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["bored", "face", "indifference", "serious", "straight face", "unamused", "not amused", "depressed", "unhappy", "disapprove", "lame"],
+    "moji": "😒"
+  },
+  "underage": {
+    "unicode": "1F51E",
+    "unicode_alternates": [],
+    "name": "no one under eighteen symbol",
+    "shortname": ":underage:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["18", "drink", "night", "pub"],
+    "moji": "🔞"
+  },
+  "unlock": {
+    "unicode": "1F513",
+    "unicode_alternates": [],
+    "name": "open lock",
+    "shortname": ":unlock:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["privacy", "security"],
+    "moji": "🔓"
+  },
+  "up": {
+    "unicode": "1F199",
+    "unicode_alternates": [],
+    "name": "squared up with exclamation mark",
+    "shortname": ":up:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square"],
+    "moji": "🆙"
+  },
+  "v": {
+    "unicode": "270C",
+    "unicode_alternates": ["270C-FE0F"],
+    "name": "victory hand",
+    "shortname": ":v:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fingers", "hand", "ohyeah", "peace", "two", "victory"],
+    "moji": "✌"
+  },
+  "vertical_traffic_light": {
+    "unicode": "1F6A6",
+    "unicode_alternates": [],
+    "name": "vertical traffic light",
+    "shortname": ":vertical_traffic_light:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["transportation", "traffic", "light", "stop", "go", "yield", "vertical"],
+    "moji": "🚦"
+  },
+  "vhs": {
+    "unicode": "1F4FC",
+    "unicode_alternates": [],
+    "name": "videocassette",
+    "shortname": ":vhs:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["oldschool", "record", "video"],
+    "moji": "📼"
+  },
+  "vibration_mode": {
+    "unicode": "1F4F3",
+    "unicode_alternates": [],
+    "name": "vibration mode",
+    "shortname": ":vibration_mode:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["orange-square", "phone"],
+    "moji": "📳"
+  },
+  "video_camera": {
+    "unicode": "1F4F9",
+    "unicode_alternates": [],
+    "name": "video camera",
+    "shortname": ":video_camera:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["film", "record"],
+    "moji": "📹"
+  },
+  "video_game": {
+    "unicode": "1F3AE",
+    "unicode_alternates": [],
+    "name": "video game",
+    "shortname": ":video_game:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["PS4", "console", "controller", "play", "video", "game", "console", "controller", "nintendo", "xbox", "playstation"],
+    "moji": "🎮"
+  },
+  "violin": {
+    "unicode": "1F3BB",
+    "unicode_alternates": [],
+    "name": "violin",
+    "shortname": ":violin:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["instrument", "music", "violin", "fiddle", "music", "instrument"],
+    "moji": "🎻"
+  },
+  "virgo": {
+    "unicode": "264D",
+    "unicode_alternates": ["264D-FE0F"],
+    "name": "virgo",
+    "shortname": ":virgo:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sign", "virgo", "maiden", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "zodiac", "horoscope"],
+    "moji": "♍"
+  },
+  "volcano": {
+    "unicode": "1F30B",
+    "unicode_alternates": [],
+    "name": "volcano",
+    "shortname": ":volcano:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "photo", "volcano", "lava", "magma", "hot", "explode"],
+    "moji": "🌋"
+  },
+  "vs": {
+    "unicode": "1F19A",
+    "unicode_alternates": [],
+    "name": "squared vs",
+    "shortname": ":vs:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["orange-square", "words"],
+    "moji": "🆚"
+  },
+  "vulcan": {
+    "unicode": "1F596",
+    "unicode_alternates": [],
+    "name": "raised hand with part between middle and ring fingers",
+    "shortname": ":vulcan:",
+    "category": "people",
+    "aliases": [":raised_hand_with_part_between_middle_and_ring_fingers:"],
+    "aliases_ascii": [],
+    "keywords": ["vulcan", "spock", "leonard", "nimoy", "star trek", "live long"]
+  },
+  "walking": {
+    "unicode": "1F6B6",
+    "unicode_alternates": [],
+    "name": "pedestrian",
+    "shortname": ":walking:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["human", "man", "walk", "pedestrian", "stroll", "stride", "foot", "feet"],
+    "moji": "🚶"
+  },
+  "waning_crescent_moon": {
+    "unicode": "1F318",
+    "unicode_alternates": [],
+    "name": "waning crescent moon symbol",
+    "shortname": ":waning_crescent_moon:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "moon", "crescent", "waning", "sky", "night", "cheese", "phase"],
+    "moji": "🌘"
+  },
+  "waning_gibbous_moon": {
+    "unicode": "1F316",
+    "unicode_alternates": [],
+    "name": "waning gibbous moon symbol",
+    "shortname": ":waning_gibbous_moon:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "moon", "waning", "gibbous", "sky", "night", "cheese", "phase"],
+    "moji": "🌖"
+  },
+  "warning": {
+    "unicode": "26A0",
+    "unicode_alternates": ["26A0-FE0F"],
+    "name": "warning sign",
+    "shortname": ":warning:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["exclamation", "wip"],
+    "moji": "⚠"
+  },
+  "wastebasket": {
+    "unicode": "1F5D1",
+    "unicode_alternates": [],
+    "name": "wastebasket",
+    "shortname": ":wastebasket:",
+    "category": "objects_symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["trash", "garbage", "dispose"]
+  },
+  "watch": {
+    "unicode": "231A",
+    "unicode_alternates": ["231A-FE0F"],
+    "name": "watch",
+    "shortname": ":watch:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["accessories", "time"],
+    "moji": "⌚"
+  },
+  "water_buffalo": {
+    "unicode": "1F403",
+    "unicode_alternates": [],
+    "name": "water buffalo",
+    "shortname": ":water_buffalo:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "cow", "nature", "ox", "water", "buffalo", "asia", "bovine", "milk", "dairy"],
+    "moji": "🐃"
+  },
+  "watermelon": {
+    "unicode": "1F349",
+    "unicode_alternates": [],
+    "name": "watermelon",
+    "shortname": ":watermelon:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["food", "fruit", "melon", "watermelon", "summer", "fruit", "large"],
+    "moji": "🍉"
+  },
+  "wave": {
+    "unicode": "1F44B",
+    "unicode_alternates": [],
+    "name": "waving hand sign",
+    "shortname": ":wave:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["farewell", "gesture", "goodbye", "hands", "solong"],
+    "moji": "👋"
+  },
+  "wavy_dash": {
+    "unicode": "3030",
+    "unicode_alternates": [],
+    "name": "wavy dash",
+    "shortname": ":wavy_dash:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["draw", "line"],
+    "moji": "〰"
+  },
+  "waxing_crescent_moon": {
+    "unicode": "1F312",
+    "unicode_alternates": [],
+    "name": "waxing crescent moon symbol",
+    "shortname": ":waxing_crescent_moon:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature", "moon", "waxing", "sky", "night", "cheese", "phase"],
+    "moji": "🌒"
+  },
+  "waxing_gibbous_moon": {
+    "unicode": "1F314",
+    "unicode_alternates": [],
+    "name": "waxing gibbous moon symbol",
+    "shortname": ":waxing_gibbous_moon:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["nature"],
+    "moji": "🌔"
+  },
+  "wc": {
+    "unicode": "1F6BE",
+    "unicode_alternates": [],
+    "name": "water closet",
+    "shortname": ":wc:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "restroom", "toilet", "water", "closet", "toilet", "bathroom", "throne", "porcelain", "waste", "flush", "plumbing"],
+    "moji": "🚾"
+  },
+  "weary": {
+    "unicode": "1F629",
+    "unicode_alternates": [],
+    "name": "weary face",
+    "shortname": ":weary:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "frustrated", "sad", "sleepy", "tired", "weary", "sleepy", "tired", "tiredness", "study", "finals", "school", "exhausted"],
+    "moji": "😩"
+  },
+  "wedding": {
+    "unicode": "1F492",
+    "unicode_alternates": [],
+    "name": "wedding",
+    "shortname": ":wedding:",
+    "category": "places",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "bride", "couple", "groom", "like", "love", "marriage"],
+    "moji": "💒"
+  },
+  "whale": {
+    "unicode": "1F433",
+    "unicode_alternates": [],
+    "name": "spouting whale",
+    "shortname": ":whale:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "ocean", "sea"],
+    "moji": "🐳"
+  },
+  "whale2": {
+    "unicode": "1F40B",
+    "unicode_alternates": [],
+    "name": "whale",
+    "shortname": ":whale2:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature", "ocean", "sea", "whale", "blubber", "bloated", "fat", "large", "massive"],
+    "moji": "🐋"
+  },
+  "wheelchair": {
+    "unicode": "267F",
+    "unicode_alternates": ["267F-FE0F"],
+    "name": "wheelchair symbol",
+    "shortname": ":wheelchair:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "disabled"],
+    "moji": "♿"
+  },
+  "white_check_mark": {
+    "unicode": "2705",
+    "unicode_alternates": [],
+    "name": "white heavy check mark",
+    "shortname": ":white_check_mark:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["agree", "green-square", "ok"],
+    "moji": "✅"
+  },
+  "white_circle": {
+    "unicode": "26AA",
+    "unicode_alternates": ["26AA-FE0F"],
+    "name": "medium white circle",
+    "shortname": ":white_circle:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "⚪"
+  },
+  "white_flower": {
+    "unicode": "1F4AE",
+    "unicode_alternates": [],
+    "name": "white flower",
+    "shortname": ":white_flower:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["japanese", "white", "flower", "teacher", "school", "grade", "score", "brilliance", "intelligence", "homework", "student", "assignment", "praise"],
+    "moji": "💮"
+  },
+  "white_large_square": {
+    "unicode": "2B1C",
+    "unicode_alternates": ["2B1C-FE0F"],
+    "name": "white large square",
+    "shortname": ":white_large_square:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "⬜"
+  },
+  "white_medium_small_square": {
+    "unicode": "25FD",
+    "unicode_alternates": ["25FD-FE0F"],
+    "name": "white medium small square",
+    "shortname": ":white_medium_small_square:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "◽"
+  },
+  "white_medium_square": {
+    "unicode": "25FB",
+    "unicode_alternates": ["25FB-FE0F"],
+    "name": "white medium square",
+    "shortname": ":white_medium_square:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "◻"
+  },
+  "white_small_square": {
+    "unicode": "25AB",
+    "unicode_alternates": ["25AB-FE0F"],
+    "name": "white small square",
+    "shortname": ":white_small_square:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "▫"
+  },
+  "white_square_button": {
+    "unicode": "1F533",
+    "unicode_alternates": [],
+    "name": "white square button",
+    "shortname": ":white_square_button:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["shape"],
+    "moji": "🔳"
+  },
+  "wind_blowing_face": {
+    "unicode": "1F32C",
+    "unicode_alternates": [],
+    "name": "wind blowing face",
+    "shortname": ":wind_blowing_face:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["mother", "nature"]
+  },
+  "wind_chime": {
+    "unicode": "1F390",
+    "unicode_alternates": [],
+    "name": "wind chime",
+    "shortname": ":wind_chime:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["ding", "nature", "wind", "chime", "bell", "fūrin", "instrument", "music", "spirits", "soothing", "protective", "spiritual", "sound"],
+    "moji": "🎐"
+  },
+  "wine_glass": {
+    "unicode": "1F377",
+    "unicode_alternates": [],
+    "name": "wine glass",
+    "shortname": ":wine_glass:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["alcohol", "beverage", "booze", "bottle", "drink", "drunk", "fermented", "glass", "grapes", "tasting", "wine", "winery"],
+    "moji": "🍷"
+  },
+  "wink": {
+    "unicode": "1F609",
+    "unicode_alternates": [],
+    "name": "winking face",
+    "shortname": ":wink:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [";)", ";-)", "*-)", "*)", ";-]", ";]", ";D", ";^)"],
+    "keywords": ["face", "happy", "mischievous", "secret", "wink", "winking", "friendly", "joke"],
+    "moji": "😉"
+  },
+  "wolf": {
+    "unicode": "1F43A",
+    "unicode_alternates": [],
+    "name": "wolf face",
+    "shortname": ":wolf:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["animal", "nature"],
+    "moji": "🐺"
+  },
+  "woman": {
+    "unicode": "1F469",
+    "unicode_alternates": [],
+    "name": "woman",
+    "shortname": ":woman:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["female", "girls"],
+    "moji": "👩"
+  },
+  "womans_clothes": {
+    "unicode": "1F45A",
+    "unicode_alternates": [],
+    "name": "womans clothes",
+    "shortname": ":womans_clothes:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["fashion", "woman", "clothing", "clothes", "blouse", "shirt", "wardrobe", "breasts", "cleavage", "shopping", "shop", "dressing", "dressed"],
+    "moji": "👚"
+  },
+  "womans_hat": {
+    "unicode": "1F452",
+    "unicode_alternates": [],
+    "name": "womans hat",
+    "shortname": ":womans_hat:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["accessories", "fashion", "female"],
+    "moji": "👒"
+  },
+  "womens": {
+    "unicode": "1F6BA",
+    "unicode_alternates": [],
+    "name": "womens symbol",
+    "shortname": ":womens:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["purple-square", "woman", "bathroom", "restroom", "sign", "girl", "female", "avatar"],
+    "moji": "🚺"
+  },
+  "worried": {
+    "unicode": "1F61F",
+    "unicode_alternates": [],
+    "name": "worried face",
+    "shortname": ":worried:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["concern", "face", "nervous", "worried", "anxious", "distressed", "nervous", "tense"],
+    "moji": "😟"
+  },
+  "wrench": {
+    "unicode": "1F527",
+    "unicode_alternates": [],
+    "name": "wrench",
+    "shortname": ":wrench:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["diy", "ikea", "tools"],
+    "moji": "🔧"
+  },
+  "writing_hand": {
+    "unicode": "1F58E",
+    "unicode_alternates": [],
+    "name": "left writing hand",
+    "shortname": ":writing_hand:",
+    "category": "people",
+    "aliases": [":left_writing_hand:"],
+    "aliases_ascii": [],
+    "keywords": ["write", "sign", "signature", "draw"]
+  },
+  "x": {
+    "unicode": "274C",
+    "unicode_alternates": [],
+    "name": "cross mark",
+    "shortname": ":x:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["delete", "no", "remove"],
+    "moji": "❌"
+  },
+  "yellow_heart": {
+    "unicode": "1F49B",
+    "unicode_alternates": [],
+    "name": "yellow heart",
+    "shortname": ":yellow_heart:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["affection", "like", "love", "valentines", "yellow", "gold", "heart", "love", "friendship", "happy", "happiness", "trust", "compassionate", "respectful", "honest", "caring", "selfless"],
+    "moji": "💛"
+  },
+  "yen": {
+    "unicode": "1F4B4",
+    "unicode_alternates": [],
+    "name": "banknote with yen sign",
+    "shortname": ":yen:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["currency", "dollar", "japanese", "money", "yen", "japan", "japanese", "banknote", "money", "currency", "paper", "cash", "bill"],
+    "moji": "💴"
+  },
+  "yum": {
+    "unicode": "1F60B",
+    "unicode_alternates": [],
+    "name": "face savouring delicious food",
+    "shortname": ":yum:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["face", "happy", "joy", "smile", "tongue", "delicious", "savoring", "food", "eat", "yummy", "yum", "tasty", "savory"],
+    "moji": "😋"
+  },
+  "zap": {
+    "unicode": "26A1",
+    "unicode_alternates": ["26A1-FE0F"],
+    "name": "high voltage sign",
+    "shortname": ":zap:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["lightning bolt", "thunder", "weather"],
+    "moji": "⚡"
+  },
+  "zero": {
+    "moji": "0️⃣",
+    "unicode": "0030-20E3",
+    "unicode_alternates": ["0030-FE0F-20E3"],
+    "name": "digit zero",
+    "shortname": ":zero:",
+    "category": "other",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["blue-square", "null", "numbers"]
+  },
+  "zzz": {
+    "unicode": "1F4A4",
+    "unicode_alternates": [],
+    "name": "sleeping symbol",
+    "shortname": ":zzz:",
+    "category": "emoticons",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": ["sleepy", "tired"],
+    "moji": "💤"
+  }
+}
diff --git a/lib/api/api.rb b/lib/api/api.rb
index fe1bf8a48160c1fd4fe244925d227e8ae9db9597..7834262d612244d7162eb33f99e9458b4937200c 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -53,5 +53,6 @@ module API
     mount Settings
     mount Keys
     mount Tags
+    mount Triggers
   end
 end
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 2c0596c9dfb64cf730fada8ac4bf16157677294d..1162271f5fcd506bb35acf0d3710ac45c90e000b 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -53,7 +53,7 @@ module API
 
         name = params[:name] || params[:context]
         status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref])
-        status ||= GenericCommitStatus.new(commit: ci_commit, user: current_user)
+        status ||= GenericCommitStatus.new(project: @project, commit: ci_commit, user: current_user)
         status.update(attrs)
 
         case params[:state].to_s
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 9f337bc3cc6692e1cc3bd6e35f1827c11fae0162..26e7c956e8f79f2bc23ba4650c9e64693d1e75b0 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -45,7 +45,8 @@ module API
 
     class ProjectHook < Hook
       expose :project_id, :push_events
-      expose :issues_events, :merge_requests_events, :tag_push_events, :note_events, :enable_ssl_verification
+      expose :issues_events, :merge_requests_events, :tag_push_events, :note_events, :build_events
+      expose :enable_ssl_verification
     end
 
     class ForkedFromProject < Grape::Entity
@@ -63,11 +64,13 @@ module API
       expose :name, :name_with_namespace
       expose :path, :path_with_namespace
       expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :created_at, :last_activity_at
+      expose :shared_runners_enabled
       expose :creator_id
       expose :namespace
-      expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? }
+      expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ |project, options| project.forked? }
       expose :avatar_url
       expose :star_count, :forks_count
+      expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? }
     end
 
     class ProjectMember < UserBasic
@@ -163,7 +166,6 @@ module API
 
     class MergeRequest < ProjectEntity
       expose :target_branch, :source_branch
-      # deprecated, always returns 0
       expose :upvotes,  :downvotes
       expose :author, :assignee, using: Entities::UserBasic
       expose :source_project_id, :target_project_id
@@ -171,6 +173,7 @@ module API
       expose :description
       expose :work_in_progress?, as: :work_in_progress
       expose :milestone, using: Entities::Milestone
+      expose :merge_when_build_succeeds
     end
 
     class MergeRequestChanges < MergeRequest
@@ -194,6 +197,7 @@ module API
       expose :author, using: Entities::UserBasic
       expose :created_at
       expose :system?, as: :system
+      expose :noteable_id, :noteable_type
       # upvote? and downvote? are deprecated, always return false
       expose :upvote?, as: :upvote
       expose :downvote?, as: :downvote
@@ -224,6 +228,8 @@ module API
       expose :target_id, :target_type, :author_id
       expose :data, :target_title
       expose :created_at
+      expose :note, using: Entities::Note, if: ->(event, options) { event.note? }
+      expose :author, using: Entities::UserBasic, if: ->(event, options) { event.author }
 
       expose :author_username do |event, options|
         if event.author
@@ -248,7 +254,7 @@ module API
 
     class ProjectService < Grape::Entity
       expose :id, :title, :created_at, :updated_at, :active
-      expose :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events
+      expose :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events, :build_events
       # Expose serialized properties
       expose :properties do |service, options|
         field_names = service.fields.
@@ -355,5 +361,9 @@ module API
         end
       end
     end
+
+    class TriggerRequest < Grape::Entity
+      expose :id, :variables
+    end
   end
 end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index a7a768f8895ded69e5bfeba3c9e38819af79e6d6..8ad2c1883c77e7b91ee86026f5217264585eb235 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -7,7 +7,7 @@ module API
       def commit_params(attrs)
         {
           file_path: attrs[:file_path],
-          current_branch: attrs[:branch_name],
+          source_branch: attrs[:branch_name],
           target_branch: attrs[:branch_name],
           commit_message: attrs[:commit_message],
           file_content: attrs[:content],
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 024aeec2e14bde0d9eb41b052af7d18f5117e59c..1a14d870a4a396e51ea76c0792112913f1248498 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -65,6 +65,18 @@ module API
         DestroyGroupService.new(group, current_user).execute
       end
 
+      # Get a list of projects in this group
+      #
+      # Example Request:
+      #   GET /groups/:id/projects
+      get ":id/projects" do
+        group = find_group(params[:id])
+        projects = group.projects
+        projects = filter_projects(projects)
+        projects = paginate projects
+        present projects, with: Entities::Project
+      end
+
       # Transfer a project to the Group namespace
       #
       # Parameters:
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 92540ccf2b106bb09cbaba7a85972e21dabfa20e..a4df810e755ce7c50bdce1316fa1d8a1f8d40724 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -266,12 +266,7 @@ module API
         projects = projects.search(params[:search])
       end
 
-      if params[:ci_enabled_first].present?
-        projects.includes(:gitlab_ci_service).
-          reorder("services.active DESC, projects.#{project_order_by} #{project_sort}")
-      else
-        projects.reorder(project_order_by => project_sort)
-      end
+      projects.reorder(project_order_by => project_sort)
     end
 
     def project_order_by
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 6eb84baf9cb0aab677a820a2f4b05ad861e832bd..3c1c6bda260db5c8c7cf10c79f5e7991a0261819 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -76,6 +76,22 @@ module API
         present merge_request, with: Entities::MergeRequest
       end
 
+      # Show MR commits
+      #
+      # Parameters:
+      #   id (required)               - The ID of a project
+      #   merge_request_id (required) - The ID of MR
+      #
+      # Example:
+      #   GET /projects/:id/merge_request/:merge_request_id/commits
+      #
+      get ':id/merge_request/:merge_request_id/commits' do
+        merge_request = user_project.merge_requests.
+          find(params[:merge_request_id])
+        authorize! :read_merge_request, merge_request
+        present merge_request.commits, with: Entities::RepoCommit
+      end
+
       # Show MR changes
       #
       # Parameters:
@@ -179,46 +195,54 @@ module API
       # Merge MR
       #
       # Parameters:
-      #   id (required)                   - The ID of a project
-      #   merge_request_id (required)     - ID of MR
-      #   merge_commit_message (optional) - Custom merge commit message
+      #   id (required)                           - The ID of a project
+      #   merge_request_id (required)             - ID of MR
+      #   merge_commit_message (optional)         - Custom merge commit message
+      #   should_remove_source_branch (optional)  - When true, the source branch will be deleted if possible
+      #   merge_when_build_succeeds (optional)    - When true, this MR will be merged when the build succeeds
       # Example:
       #   PUT /projects/:id/merge_request/:merge_request_id/merge
       #
       put ":id/merge_request/:merge_request_id/merge" do
         merge_request = user_project.merge_requests.find(params[:merge_request_id])
 
-        allowed = ::Gitlab::GitAccess.new(current_user, user_project).
-          can_push_to_branch?(merge_request.target_branch)
+        # Merge request can not be merged
+        # because user dont have permissions to push into target branch
+        unauthorized! unless merge_request.can_be_merged_by?(current_user)
+        not_allowed! if !merge_request.open? || merge_request.work_in_progress?
 
-        if allowed
-          if merge_request.unchecked?
-            merge_request.check_if_can_be_merged
-          end
+        merge_request.check_if_can_be_merged if merge_request.unchecked?
 
-          if merge_request.open? && !merge_request.work_in_progress?
-            if merge_request.can_be_merged?
-              commit_message = params[:merge_commit_message] || merge_request.merge_commit_message
-
-              ::MergeRequests::MergeService.new(merge_request.target_project, current_user).
-                execute(merge_request, commit_message)
-
-              present merge_request, with: Entities::MergeRequest
-            else
-              render_api_error!('Branch cannot be merged', 405)
-            end
-          else
-            # Merge request can not be merged
-            # because it is already closed/merged or marked as WIP
-            not_allowed!
-          end
+        render_api_error!('Branch cannot be merged', 406) unless merge_request.can_be_merged?
+
+        merge_params = {
+          commit_message: params[:merge_commit_message],
+          should_remove_source_branch: params[:should_remove_source_branch]
+        }
+
+        if parse_boolean(params[:merge_when_build_succeeds]) && merge_request.ci_commit && merge_request.ci_commit.active?
+          ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params).
+            execute(merge_request)
         else
-          # Merge request can not be merged
-          # because user dont have permissions to push into target branch
-          unauthorized!
+          ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params).
+            execute(merge_request)
         end
+
+        present merge_request, with: Entities::MergeRequest
       end
 
+      # Cancel Merge if Merge When build succeeds is enabled
+      # Parameters:
+      #   id (required)                         - The ID of a project
+      #   merge_request_id (required)           - ID of MR
+      #
+      post ":id/merge_request/:merge_request_id/cancel_merge_when_build_succeeds" do
+        merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+        unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user)
+
+        ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request)
+      end
 
       # Get a merge request's comments
       #
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 882d1a083ad828d2ea6681b0ca2e112bc636aac4..cf9938d25a7ff9b6bf328701f162fc0a605c255e 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -45,6 +45,7 @@ module API
           :merge_requests_events,
           :tag_push_events,
           :note_events,
+          :build_events,
           :enable_ssl_verification
         ]
         @hook = user_project.hooks.new(attrs)
@@ -77,6 +78,7 @@ module API
           :merge_requests_events,
           :tag_push_events,
           :note_events,
+          :build_events,
           :enable_ssl_verification
         ]
 
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 2b4ada6e2ebbf2e3861128d0788b0adf78284308..a9e0960872a08cb1b455a448b8458364acd95e8d 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -7,8 +7,12 @@ module API
       helpers do
         def map_public_to_visibility_level(attrs)
           publik = attrs.delete(:public)
-          publik = parse_boolean(publik)
-          attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true
+          if publik.present? && !attrs[:visibility_level].present?
+            publik = parse_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
+          end
           attrs
         end
       end
@@ -21,7 +25,7 @@ module API
         @projects = current_user.authorized_projects
         @projects = filter_projects(@projects)
         @projects = paginate @projects
-        present @projects, with: Entities::Project
+        present @projects, with: Entities::ProjectWithAccess, user: current_user
       end
 
       # Get an owned projects list for authenticated user
@@ -32,6 +36,17 @@ module API
         @projects = current_user.owned_projects
         @projects = filter_projects(@projects)
         @projects = paginate @projects
+        present @projects, with: Entities::ProjectWithAccess, user: current_user
+      end
+
+      # Gets starred project for the authenticated user
+      #
+      # Example Request:
+      #   GET /projects/starred
+      get '/starred' do
+        @projects = current_user.starred_projects
+        @projects = filter_projects(@projects)
+        @projects = paginate @projects
         present @projects, with: Entities::Project
       end
 
@@ -44,7 +59,7 @@ module API
         @projects = Project.all
         @projects = filter_projects(@projects)
         @projects = paginate @projects
-        present @projects, with: Entities::Project
+        present @projects, with: Entities::ProjectWithAccess, user: current_user
       end
 
       # Get a single project
@@ -78,6 +93,7 @@ module API
       #   builds_enabled (optional)
       #   wiki_enabled (optional)
       #   snippets_enabled (optional)
+      #   shared_runners_enabled (optional)
       #   namespace_id (optional) - defaults to user namespace
       #   public (optional) - if true same as setting visibility_level = 20
       #   visibility_level (optional) - 0 by default
@@ -94,6 +110,7 @@ module API
                                      :builds_enabled,
                                      :wiki_enabled,
                                      :snippets_enabled,
+                                     :shared_runners_enabled,
                                      :namespace_id,
                                      :public,
                                      :visibility_level,
@@ -122,6 +139,7 @@ module API
       #   builds_enabled (optional)
       #   wiki_enabled (optional)
       #   snippets_enabled (optional)
+      #   shared_runners_enabled (optional)
       #   public (optional) - if true same as setting visibility_level = 20
       #   visibility_level (optional)
       #   import_url (optional)
@@ -138,6 +156,7 @@ module API
                                      :builds_enabled,
                                      :wiki_enabled,
                                      :snippets_enabled,
+                                     :shared_runners_enabled,
                                      :public,
                                      :visibility_level,
                                      :import_url]
@@ -179,6 +198,7 @@ module API
       #   builds_enabled (optional)
       #   wiki_enabled (optional)
       #   snippets_enabled (optional)
+      #   shared_runners_enabled (optional)
       #   public (optional) - if true same as setting visibility_level = 20
       #   visibility_level (optional) - visibility level of a project
       # Example Request
@@ -193,6 +213,7 @@ module API
                                      :builds_enabled,
                                      :wiki_enabled,
                                      :snippets_enabled,
+                                     :shared_runners_enabled,
                                      :public,
                                      :visibility_level]
         attrs = map_public_to_visibility_level(attrs)
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2781f1cf1917dda336d4c1931483908c3c69560f
--- /dev/null
+++ b/lib/api/triggers.rb
@@ -0,0 +1,48 @@
+module API
+  # Triggers API
+  class Triggers < Grape::API
+    resource :projects do
+      # Trigger a GitLab project build
+      #
+      # Parameters:
+      #   id (required) - The ID of a CI project
+      #   ref (required) - The name of project's branch or tag
+      #   token (required) - The uniq token of trigger
+      #   variables (optional) - The list of variables to be injected into build
+      # Example Request:
+      #   POST /projects/:id/trigger/builds
+      post ":id/trigger/builds" do
+        required_attributes! [:ref, :token]
+
+        project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id])
+        trigger = Ci::Trigger.find_by_token(params[:token].to_s)
+        not_found! unless project && trigger
+        unauthorized! unless trigger.project == project
+
+        # validate variables
+        variables = params[:variables]
+        if variables
+          unless variables.is_a?(Hash)
+            render_api_error!('variables needs to be a hash', 400)
+          end
+
+          unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
+            render_api_error!('variables needs to be a map of key-valued strings', 400)
+          end
+
+          # convert variables from Mash to Hash
+          variables = variables.to_h
+        end
+
+        # create request and trigger builds
+        trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
+        if trigger_request
+          present trigger_request, with: Entities::TriggerRequest
+        else
+          errors = 'No builds created'
+          render_api_error!(errors, 400)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index a98d668e02d0db0c36719212d83bae75fcbb7c5a..3400f0713ef11c010e93680f41dd3a6e2e8796fb 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -8,11 +8,17 @@ module API
       #
       # Example Request:
       #  GET /users
+      #  GET /users?search=Admin
+      #  GET /users?username=root
       get do
-        @users = User.all
-        @users = @users.active if params[:active].present?
-        @users = @users.search(params[:search]) if params[:search].present?
-        @users = paginate @users
+        if params[:username].present?
+          @users = User.where(username: params[:username])
+        else
+          @users = User.all
+          @users = @users.active if params[:active].present?
+          @users = @users.search(params[:search]) if params[:search].present?
+          @users = paginate @users
+        end
 
         if current_user.is_admin?
           present @users, with: Entities::UserFull
diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb
index d58a196c4efda38b8875ea78256072b0818a2a97..783fcfb61ad0363326eac5abff1970e1de318814 100644
--- a/lib/award_emoji.rb
+++ b/lib/award_emoji.rb
@@ -1,12 +1,51 @@
 class AwardEmoji
-  EMOJI_LIST = [
-    "+1", "-1", "100", "blush", "heart", "smile", "rage",
-    "beers", "disappointed", "ok_hand",
-    "helicopter", "shit", "airplane", "alarm_clock",
-    "ambulance", "anguished", "two_hearts", "wink"
-  ]
-
-  def self.path_to_emoji_image(name)
-    "emoji/#{Emoji.emoji_filename(name)}.png"
+  CATEGORIES = {
+    other: "Other",
+    objects: "Objects",
+    places: "Places",
+    travel_places: "Travel",
+    emoticons: "Emoticons",
+    objects_symbols: "Symbols",
+    nature: "Nature",
+    celebration: "Celebration",
+    people: "People",
+    activity: "Activity",
+    flags: "Flags",
+    food_drink: "Food"
+  }.with_indifferent_access
+
+  def self.normilize_emoji_name(name)
+    aliases[name] || name
+  end
+
+  def self.emoji_by_category
+    unless @emoji_by_category
+      @emoji_by_category = {}
+
+      emojis.each do |emoji_name, data|
+        data["name"] = emoji_name
+
+        @emoji_by_category[data["category"]] ||= []
+        @emoji_by_category[data["category"]] << data
+      end
+
+      @emoji_by_category = @emoji_by_category.sort.to_h
+    end
+
+    @emoji_by_category
+  end
+
+  def self.emojis
+    @emojis ||= begin
+      json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' )
+      JSON.parse(File.read(json_path))
+    end
+  end
+
+  def self.aliases
+    @aliases ||= begin
+      json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' )
+      JSON.parse(File.read(json_path))
+    end
   end
 end
diff --git a/lib/banzai.rb b/lib/banzai.rb
new file mode 100644
index 0000000000000000000000000000000000000000..093382261ae8552645b1c3cd2cb44ce8c0bb3538
--- /dev/null
+++ b/lib/banzai.rb
@@ -0,0 +1,13 @@
+module Banzai
+  def self.render(text, context = {})
+    Renderer.render(text, context)
+  end
+
+  def self.render_result(text, context = {})
+    Renderer.render_result(text, context)
+  end
+
+  def self.post_process(html, context)
+    Renderer.post_process(html, context)
+  end
+end
diff --git a/lib/banzai/cross_project_reference.rb b/lib/banzai/cross_project_reference.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ba2866e1efaa2f49222c41a64fd7599afb2ee94a
--- /dev/null
+++ b/lib/banzai/cross_project_reference.rb
@@ -0,0 +1,22 @@
+require 'banzai'
+
+module Banzai
+  # Common methods for ReferenceFilters that support an optional cross-project
+  # reference.
+  module CrossProjectReference
+    # Given a cross-project reference string, get the Project record
+    #
+    # Defaults to value of `context[:project]` if:
+    # * No reference is given OR
+    # * Reference given doesn't exist
+    #
+    # ref - String reference.
+    #
+    # Returns a Project, or nil if the reference can't be found
+    def project_from_ref(ref)
+      return context[:project] unless ref
+
+      Project.find_with_namespace(ref)
+    end
+  end
+end
diff --git a/lib/banzai/filter.rb b/lib/banzai/filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fd4fe024252db0605edda3d7c5e71170801e7030
--- /dev/null
+++ b/lib/banzai/filter.rb
@@ -0,0 +1,10 @@
+require 'active_support/core_ext/string/output_safety'
+require 'banzai'
+
+module Banzai
+  module Filter
+    def self.[](name)
+      const_get("#{name.to_s.camelize}Filter")
+    end
+  end
+end
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..63ad8910c0ff31b774051f4e66a4ea145a041a21
--- /dev/null
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -0,0 +1,148 @@
+require 'banzai'
+
+module Banzai
+  module Filter
+    # Issues, Merge Requests, Snippets, Commits and Commit Ranges share
+    # similar functionality in reference filtering.
+    class AbstractReferenceFilter < ReferenceFilter
+      include CrossProjectReference
+
+      def self.object_class
+        # Implement in child class
+        # Example: MergeRequest
+      end
+
+      def self.object_name
+        object_class.name.underscore
+      end
+
+      def self.object_sym
+        object_name.to_sym
+      end
+
+      def self.data_reference
+        "data-#{object_name.dasherize}"
+      end
+
+      # Public: Find references in text (like `!123` for merge requests)
+      #
+      #   AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches|
+      #     object = find_object(project_ref, id)
+      #     "<a href=...>#{object.to_reference}</a>"
+      #   end
+      #
+      # text - String text to search.
+      #
+      # Yields the String match, the Integer referenced object ID, an optional String
+      # of the external project reference, and all of the matchdata.
+      #
+      # Returns a String replaced with the return of the block.
+      def self.references_in(text, pattern = object_class.reference_pattern)
+        text.gsub(pattern) do |match|
+          yield match, $~[object_sym].to_i, $~[:project], $~
+        end
+      end
+
+      def self.referenced_by(node)
+        { object_sym => LazyReference.new(object_class, node.attr(data_reference)) }
+      end
+
+      delegate :object_class, :object_sym, :references_in, to: :class
+
+      def find_object(project, id)
+        # Implement in child class
+        # Example: project.merge_requests.find
+      end
+
+      def url_for_object(object, project)
+        # Implement in child class
+        # Example: project_merge_request_url
+      end
+
+      def call
+        # `#123`
+        replace_text_nodes_matching(object_class.reference_pattern) do |content|
+          object_link_filter(content, object_class.reference_pattern)
+        end
+
+        # `[Issue](#123)`, which is turned into
+        # `<a href="#123">Issue</a>`
+        replace_link_nodes_with_href(object_class.reference_pattern) do |link, text|
+          object_link_filter(link, object_class.reference_pattern, link_text: text)
+        end
+
+        # `http://gitlab.example.com/namespace/project/issues/123`, which is turned into
+        # `<a href="http://gitlab.example.com/namespace/project/issues/123">http://gitlab.example.com/namespace/project/issues/123</a>`
+        replace_link_nodes_with_text(object_class.link_reference_pattern) do |text|
+          object_link_filter(text, object_class.link_reference_pattern)
+        end
+
+        # `[Issue](http://gitlab.example.com/namespace/project/issues/123)`, which is turned into
+        # `<a href="http://gitlab.example.com/namespace/project/issues/123">Issue</a>`
+        replace_link_nodes_with_href(object_class.link_reference_pattern) do |link, text|
+          object_link_filter(link, object_class.link_reference_pattern, link_text: text)
+        end
+      end
+
+      # Replace references (like `!123` for merge requests) in text with links
+      # to the referenced object's details page.
+      #
+      # text - String text to replace references in.
+      # pattern - Reference pattern to match against.
+      # link_text - Original content of the link being replaced.
+      #
+      # Returns a String with references replaced with links. All links
+      # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
+      def object_link_filter(text, pattern, link_text: nil)
+        references_in(text, pattern) do |match, id, project_ref, matches|
+          project = project_from_ref(project_ref)
+
+          if project && object = find_object(project, id)
+            title = object_link_title(object)
+            klass = reference_class(object_sym)
+
+            data  = data_attribute(
+              original:     link_text || match,
+              project:      project.id,
+              object_sym => object.id
+            )
+
+            url = matches[:url] if matches.names.include?("url")
+            url ||= url_for_object(object, project)
+
+            text = link_text || object_link_text(object, matches)
+
+            %(<a href="#{url}" #{data}
+                 title="#{escape_once(title)}"
+                 class="#{klass}">#{escape_once(text)}</a>)
+          else
+            match
+          end
+        end
+      end
+
+      def object_link_text_extras(object, matches)
+        extras = []
+
+        if matches.names.include?("anchor") && matches[:anchor] && matches[:anchor] =~ /\A\#note_(\d+)\z/
+          extras << "comment #{$1}"
+        end
+
+        extras
+      end
+
+      def object_link_title(object)
+        "#{object_class.name.titleize}: #{object.title}"
+      end
+
+      def object_link_text(object, matches)
+        text = object.reference_link_text(context[:project])
+
+        extras = object_link_text_extras(object, matches)
+        text += " (#{extras.join(", ")})" if extras.any?
+
+        text
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
similarity index 98%
rename from lib/gitlab/markdown/autolink_filter.rb
rename to lib/banzai/filter/autolink_filter.rb
index c37c3bc55bf5ca79bb5da2cbfeaccfd8c7698d32..da4ee80c1b5cadf5de6ae385251052390b5f8d72 100644
--- a/lib/gitlab/markdown/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -1,9 +1,9 @@
-require 'gitlab/markdown'
+require 'banzai'
 require 'html/pipeline/filter'
 require 'uri'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML Filter for auto-linking URLs in HTML.
     #
     # Based on HTML::Pipeline::AutolinkFilter
diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e67cd45ab9bd1159c6bcd5ea3f6fc762d0ee0a69
--- /dev/null
+++ b/lib/banzai/filter/commit_range_reference_filter.rb
@@ -0,0 +1,58 @@
+require 'banzai'
+
+module Banzai
+  module Filter
+    # HTML filter that replaces commit range references with links.
+    #
+    # This filter supports cross-project references.
+    class CommitRangeReferenceFilter < AbstractReferenceFilter
+      def self.object_class
+        CommitRange
+      end
+
+      def self.references_in(text, pattern = CommitRange.reference_pattern)
+        text.gsub(pattern) do |match|
+          yield match, $~[:commit_range], $~[:project], $~
+        end
+      end
+
+      def self.referenced_by(node)
+        project = Project.find(node.attr("data-project")) rescue nil
+        return unless project
+
+        id = node.attr("data-commit-range")
+        range = find_object(project, id)
+
+        return unless range
+
+        { commit_range: range }
+      end
+
+      def initialize(*args)
+        super
+
+        @commit_map = {}
+      end
+
+      def self.find_object(project, id)
+        range = CommitRange.new(id, project)
+
+        range.valid_commits? ? range : nil
+      end
+
+      def find_object(*args)
+        self.class.find_object(*args)
+      end
+
+      def url_for_object(range, project)
+        h = Gitlab::Application.routes.url_helpers
+        h.namespace_project_compare_url(project.namespace, project,
+                                        range.to_param.merge(only_path: context[:only_path]))
+      end
+
+      def object_link_title(range)
+        range.reference_title
+      end
+    end
+  end
+end
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9e57608b483651dec1449ee72d2121cbee59cfb9
--- /dev/null
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -0,0 +1,63 @@
+require 'banzai'
+
+module Banzai
+  module Filter
+    # HTML filter that replaces commit references with links.
+    #
+    # This filter supports cross-project references.
+    class CommitReferenceFilter < AbstractReferenceFilter
+      def self.object_class
+        Commit
+      end
+
+      def self.references_in(text, pattern = Commit.reference_pattern)
+        text.gsub(pattern) do |match|
+          yield match, $~[:commit], $~[:project], $~
+        end
+      end
+
+      def self.referenced_by(node)
+        project = Project.find(node.attr("data-project")) rescue nil
+        return unless project
+
+        id = node.attr("data-commit")
+        commit = find_object(project, id)
+
+        return unless commit
+
+        { commit: commit }
+      end
+
+      def self.find_object(project, id)
+        if project && project.valid_repo?
+          project.commit(id)
+        end
+      end
+
+      def find_object(*args)
+        self.class.find_object(*args)
+      end
+
+      def url_for_object(commit, project)
+        h = Gitlab::Application.routes.url_helpers
+        h.namespace_project_commit_url(project.namespace, project, commit,
+                                        only_path: context[:only_path])
+      end
+
+      def object_link_title(commit)
+        commit.link_title
+      end
+
+      def object_link_text_extras(object, matches)
+        extras = super
+
+        path = matches[:path] if matches.names.include?("path")
+        if path == '/builds'
+          extras.unshift "builds"
+        end
+
+        extras
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
similarity index 97%
rename from lib/gitlab/markdown/emoji_filter.rb
rename to lib/banzai/filter/emoji_filter.rb
index da10e4d3760c6775722734a4636ae6b553ea4bd8..86838e1483c40511a15cdd03c7eb3d3e72dc6e2f 100644
--- a/lib/gitlab/markdown/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -1,10 +1,10 @@
 require 'action_controller'
-require 'gitlab/markdown'
+require 'banzai'
 require 'gitlab_emoji'
 require 'html/pipeline/filter'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML filter that replaces :emoji: with images.
     #
     # Based on HTML::Pipeline::EmojiFilter
diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb
similarity index 61%
rename from lib/gitlab/markdown/external_issue_reference_filter.rb
rename to lib/banzai/filter/external_issue_reference_filter.rb
index 8f86f13976abc847adb81542675fb4177a970c1d..6136e73c096ab7df26c281a903840b64695e0803 100644
--- a/lib/gitlab/markdown/external_issue_reference_filter.rb
+++ b/lib/banzai/filter/external_issue_reference_filter.rb
@@ -1,7 +1,7 @@
-require 'gitlab/markdown'
+require 'banzai'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML filter that replaces external issue tracker references with links.
     # References are ignored if the project doesn't use an external issue
     # tracker.
@@ -23,6 +23,18 @@ module Gitlab
         end
       end
 
+      def self.referenced_by(node)
+        project = Project.find(node.attr("data-project")) rescue nil
+        return unless project
+
+        id = node.attr("data-external-issue")
+        external_issue = ExternalIssue.new(id, project)
+
+        return unless external_issue
+
+        { external_issue: external_issue }
+      end
+
       def call
         # Early return if the project isn't using an external tracker
         return doc if project.nil? || project.default_issues_tracker?
@@ -30,6 +42,10 @@ module Gitlab
         replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content|
           issue_link_filter(content)
         end
+
+        replace_link_nodes_with_href(ExternalIssue.reference_pattern) do |link, text|
+          issue_link_filter(link, link_text: text)
+        end
       end
 
       # Replace `JIRA-123` issue references in text with links to the referenced
@@ -39,19 +55,23 @@ module Gitlab
       #
       # Returns a String with `JIRA-123` references replaced with links. All
       # links have `gfm` and `gfm-issue` class names attached for styling.
-      def issue_link_filter(text)
+      def issue_link_filter(text, link_text: nil)
         project = context[:project]
 
-        self.class.references_in(text) do |match, issue|
-          url = url_for_issue(issue, project, only_path: context[:only_path])
+        self.class.references_in(text) do |match, id|
+          ExternalIssue.new(id, project)
 
-          title = escape_once("Issue in #{project.external_issue_tracker.title}")
+          url = url_for_issue(id, project, only_path: context[:only_path])
+
+          title = "Issue in #{project.external_issue_tracker.title}"
           klass = reference_class(:issue)
-          data  = data_attribute(project: project.id)
+          data  = data_attribute(project: project.id, external_issue: id)
+
+          text = link_text || match
 
           %(<a href="#{url}" #{data}
-               title="#{title}"
-               class="#{klass}">#{match}</a>)
+               title="#{escape_once(title)}"
+               class="#{klass}">#{escape_once(text)}</a>)
         end
       end
 
diff --git a/lib/gitlab/markdown/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb
similarity index 79%
rename from lib/gitlab/markdown/external_link_filter.rb
rename to lib/banzai/filter/external_link_filter.rb
index 29e51b6ade672d056230e683490d3d4ea95d6dec..ac87b9820afe7635cd89ed2ffe98782f6e56c727 100644
--- a/lib/gitlab/markdown/external_link_filter.rb
+++ b/lib/banzai/filter/external_link_filter.rb
@@ -1,16 +1,16 @@
-require 'gitlab/markdown'
+require 'banzai'
 require 'html/pipeline/filter'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML Filter to add a `rel="nofollow"` attribute to external links
     #
     class ExternalLinkFilter < HTML::Pipeline::Filter
       def call
         doc.search('a').each do |node|
-          next unless node.has_attribute?('href')
+          link = node.attr('href')
 
-          link = node.attribute('href').value
+          next unless link
 
           # Skip non-HTTP(S) links
           next unless link.start_with?('http')
diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
similarity index 89%
rename from lib/gitlab/markdown/issue_reference_filter.rb
rename to lib/banzai/filter/issue_reference_filter.rb
index 1ed69e2f43195a6ea4c5ad02482ec4fbba908142..51180cb901a62f4eeb72853e7f9098e748bf26e4 100644
--- a/lib/gitlab/markdown/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -1,7 +1,7 @@
-require 'gitlab/markdown'
+require 'banzai'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML filter that replaces issue references with links. References to
     # issues that do not exist are ignored.
     #
diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
similarity index 76%
rename from lib/gitlab/markdown/label_reference_filter.rb
rename to lib/banzai/filter/label_reference_filter.rb
index 618acb7a57814c32c1bc31f131a2d6cde8fdb40d..a3a7a23c1e6ae272f950dcb76154c837d830c3e0 100644
--- a/lib/gitlab/markdown/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -1,7 +1,7 @@
-require 'gitlab/markdown'
+require 'banzai'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML filter that replaces label references with links.
     class LabelReferenceFilter < ReferenceFilter
       # Public: Find label references in text
@@ -30,6 +30,10 @@ module Gitlab
         replace_text_nodes_matching(Label.reference_pattern) do |content|
           label_link_filter(content)
         end
+
+        replace_link_nodes_with_href(Label.reference_pattern) do |link, text|
+          label_link_filter(link, link_text: text)
+        end
       end
 
       # Replace label references in text with links to the label specified.
@@ -38,7 +42,7 @@ module Gitlab
       #
       # Returns a String with label references replaced with links. All links
       # have `gfm` and `gfm-label` class names attached for styling.
-      def label_link_filter(text)
+      def label_link_filter(text, link_text: nil)
         project = context[:project]
 
         self.class.references_in(text) do |match, id, name|
@@ -47,10 +51,16 @@ module Gitlab
           if label = project.labels.find_by(params)
             url = url_for_label(project, label)
             klass = reference_class(:label)
-            data = data_attribute(project: project.id, label: label.id)
+            data = data_attribute(
+              original: link_text || match,
+              project: project.id,
+              label: label.id
+            )
+
+            text = link_text || render_colored_label(label)
 
             %(<a href="#{url}" #{data}
-                 class="#{klass}">#{render_colored_label(label)}</a>)
+                 class="#{klass}">#{escape_once(text)}</a>)
           else
             match
           end
@@ -59,9 +69,8 @@ module Gitlab
 
       def url_for_label(project, label)
         h = Gitlab::Application.routes.url_helpers
-        h.namespace_project_issues_path(project.namespace, project,
-                                        label_name: label.name,
-                                        only_path: context[:only_path])
+        h.namespace_project_issues_url( project.namespace, project, label_name: label.name,
+                                                                    only_path:  context[:only_path])
       end
 
       def render_colored_label(label)
diff --git a/lib/banzai/filter/markdown_filter.rb b/lib/banzai/filter/markdown_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d09cf41df394d478a04f125da729b2f13e03f235
--- /dev/null
+++ b/lib/banzai/filter/markdown_filter.rb
@@ -0,0 +1,42 @@
+require 'banzai'
+require 'html/pipeline/filter'
+
+module Banzai
+  module Filter
+    class MarkdownFilter < HTML::Pipeline::TextFilter
+      def initialize(text, context = nil, result = nil)
+        super text, context, result
+        @text = @text.delete "\r"
+      end
+
+      def call
+        html = self.class.renderer.render(@text)
+        html.rstrip!
+        html
+      end
+
+      private
+
+      def self.redcarpet_options
+        # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
+        @redcarpet_options ||= {
+          fenced_code_blocks:  true,
+          footnotes:           true,
+          lax_spacing:         true,
+          no_intra_emphasis:   true,
+          space_after_headers: true,
+          strikethrough:       true,
+          superscript:         true,
+          tables:              true
+        }.freeze
+      end
+
+      def self.renderer
+        @renderer ||= begin
+          renderer = Redcarpet::Render::HTML.new
+          Redcarpet::Markdown.new(renderer, redcarpet_options)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
similarity index 62%
rename from lib/gitlab/markdown/merge_request_reference_filter.rb
rename to lib/banzai/filter/merge_request_reference_filter.rb
index 1f47f03c94ebf5f75041c23b2b80cee7fcc0cb4d..755b946a34ba8124d73a25633f9f0913c494b94a 100644
--- a/lib/gitlab/markdown/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -1,7 +1,7 @@
-require 'gitlab/markdown'
+require 'banzai'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML filter that replaces merge request references with links. References
     # to merge requests that do not exist are ignored.
     #
@@ -20,6 +20,22 @@ module Gitlab
         h.namespace_project_merge_request_url(project.namespace, project, mr,
                                             only_path: context[:only_path])
       end
+
+      def object_link_text_extras(object, matches)
+        extras = super
+
+        path = matches[:path] if matches.names.include?("path")
+        case path
+        when '/diffs'
+          extras.unshift "diffs"
+        when '/commits'
+          extras.unshift "commits"
+        when '/builds'
+          extras.unshift "builds"
+        end
+
+        extras
+      end
     end
   end
 end
diff --git a/lib/gitlab/markdown/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb
similarity index 55%
rename from lib/gitlab/markdown/redactor_filter.rb
rename to lib/banzai/filter/redactor_filter.rb
index a1f3a8a8ebfc180a02fcf64b437947f9f6426e07..f01a32b5ae5e6055f69183e5005f7b08b8062fc7 100644
--- a/lib/gitlab/markdown/redactor_filter.rb
+++ b/lib/banzai/filter/redactor_filter.rb
@@ -1,8 +1,8 @@
-require 'gitlab/markdown'
+require 'banzai'
 require 'html/pipeline/filter'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML filter that removes references to records that the current user does
     # not have permission to view.
     #
@@ -11,8 +11,11 @@ module Gitlab
     class RedactorFilter < HTML::Pipeline::Filter
       def call
         doc.css('a.gfm').each do |node|
-          unless user_can_reference?(node)
-            node.replace(node.text)
+          unless user_can_see_reference?(node)
+            # The reference should be replaced by the original text,
+            # which is not always the same as the rendered text.
+            text = node.attr('data-original') || node.text
+            node.replace(text)
           end
         end
 
@@ -21,12 +24,12 @@ module Gitlab
 
       private
 
-      def user_can_reference?(node)
+      def user_can_see_reference?(node)
         if node.has_attribute?('data-reference-filter')
           reference_type = node.attr('data-reference-filter')
-          reference_filter = reference_type.constantize
+          reference_filter = Banzai::Filter.const_get(reference_type)
 
-          reference_filter.user_can_reference?(current_user, node, context)
+          reference_filter.user_can_see_reference?(current_user, node, context)
         else
           true
         end
diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
similarity index 55%
rename from lib/gitlab/markdown/reference_filter.rb
rename to lib/banzai/filter/reference_filter.rb
index a4c560f578cae024b180fb3d05c0f0dd6e85f215..8ca05ace88cc55ea168935ed7d245b8ba61a7b40 100644
--- a/lib/gitlab/markdown/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -1,9 +1,9 @@
 require 'active_support/core_ext/string/output_safety'
-require 'gitlab/markdown'
+require 'banzai'
 require 'html/pipeline/filter'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # Base class for GitLab Flavored Markdown reference filters.
     #
     # References within <pre>, <code>, <a>, and <style> elements are ignored.
@@ -12,24 +12,7 @@ module Gitlab
     #   :project (required) - Current project, ignored if reference is cross-project.
     #   :only_path          - Generate path-only links.
     class ReferenceFilter < HTML::Pipeline::Filter
-      LazyReference = Struct.new(:klass, :ids) do
-        def self.load(refs)
-          lazy_references, values = refs.partition { |ref| ref.is_a?(self) }
-
-          lazy_values = lazy_references.group_by(&:klass).flat_map do |klass, refs|
-            ids = refs.flat_map(&:ids)
-            klass.where(id: ids)
-          end
-
-          values + lazy_values
-        end
-
-        def load
-          self.klass.where(id: self.ids)
-        end
-      end
-
-      def self.user_can_reference?(user, node, context)
+      def self.user_can_see_reference?(user, node, context)
         if node.has_attribute?('data-project')
           project_id = node.attr('data-project').to_i
           return true if project_id == context[:project].try(:id)
@@ -41,6 +24,10 @@ module Gitlab
         end
       end
 
+      def self.user_can_reference?(user, node, context)
+        true
+      end
+
       def self.referenced_by(node)
         raise NotImplementedError, "#{self} does not implement #{__method__}"
       end
@@ -53,19 +40,19 @@ module Gitlab
       # Examples:
       #
       #   data_attribute(project: 1, issue: 2)
-      #   # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"1\" data-issue=\"2\""
+      #   # => "data-reference-filter=\"SomeReferenceFilter\" data-project=\"1\" data-issue=\"2\""
       #
       #   data_attribute(project: 3, merge_request: 4)
-      #   # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"3\" data-merge-request=\"4\""
+      #   # => "data-reference-filter=\"SomeReferenceFilter\" data-project=\"3\" data-merge-request=\"4\""
       #
       # Returns a String
       def data_attribute(attributes = {})
-        attributes[:reference_filter] = self.class.name
-        attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{value}") }.join(" ")
+        attributes[:reference_filter] = self.class.name.demodulize
+        attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{escape_once(value)}") }.join(" ")
       end
 
       def escape_once(html)
-        ERB::Util.html_escape_once(html)
+        html.html_safe? ? html : ERB::Util.html_escape_once(html)
       end
 
       def ignore_parents
@@ -122,6 +109,80 @@ module Gitlab
         doc
       end
 
+      # Iterate through the document's link nodes, yielding the current node's
+      # content if:
+      #
+      # * The `project` context value is present AND
+      # * The node's content matches `pattern`
+      #
+      # pattern - Regex pattern against which to match the node's content
+      #
+      # Yields the current node's String contents. The result of the block will
+      # replace the node and update the current document.
+      #
+      # Returns the updated Nokogiri::HTML::DocumentFragment object.
+      def replace_link_nodes_with_text(pattern)
+        return doc if project.nil?
+
+        doc.search('a').each do |node|
+          klass = node.attr('class')
+          next if klass && klass.include?('gfm')
+
+          link = node.attr('href')
+          text = node.text
+
+          next unless link && text
+
+          link = URI.decode(link)
+          # Ignore ending punctionation like periods or commas
+          next unless link == text && text =~ /\A#{pattern}/
+
+          html = yield text
+
+          next if html == text
+
+          node.replace(html)
+        end
+
+        doc
+      end
+
+      # Iterate through the document's link nodes, yielding the current node's
+      # content if:
+      #
+      # * The `project` context value is present AND
+      # * The node's HREF matches `pattern`
+      #
+      # pattern - Regex pattern against which to match the node's HREF
+      #
+      # Yields the current node's String HREF and String content.
+      # The result of the block will replace the node and update the current document.
+      #
+      # Returns the updated Nokogiri::HTML::DocumentFragment object.
+      def replace_link_nodes_with_href(pattern)
+        return doc if project.nil?
+
+        doc.search('a').each do |node|
+          klass = node.attr('class')
+          next if klass && klass.include?('gfm')
+
+          link = node.attr('href')
+          text = node.text
+
+          next unless link && text
+          link = URI.decode(link)
+          next unless link && link =~ /\A#{pattern}\z/
+
+          html = yield link, text
+
+          next if html == link
+
+          node.replace(html)
+        end
+
+        doc
+      end
+
       # Ensure that a :project key exists in context
       #
       # Note that while the key might exist, its value could be nil!
diff --git a/lib/gitlab/markdown/reference_gatherer_filter.rb b/lib/banzai/filter/reference_gatherer_filter.rb
similarity index 73%
rename from lib/gitlab/markdown/reference_gatherer_filter.rb
rename to lib/banzai/filter/reference_gatherer_filter.rb
index 00f983675e6b90ba15b344e4ec56bd4ffc61c606..12412ff7ea9267bc32244758d76edef439724669 100644
--- a/lib/gitlab/markdown/reference_gatherer_filter.rb
+++ b/lib/banzai/filter/reference_gatherer_filter.rb
@@ -1,8 +1,8 @@
-require 'gitlab/markdown'
+require 'banzai'
 require 'html/pipeline/filter'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML filter that gathers all referenced records that the current user has
     # permission to view.
     #
@@ -20,7 +20,7 @@ module Gitlab
           gather_references(node)
         end
 
-        load_lazy_references unless context[:load_lazy_references] == false
+        load_lazy_references unless ReferenceExtractor.lazy?
 
         doc
       end
@@ -31,11 +31,13 @@ module Gitlab
         return unless node.has_attribute?('data-reference-filter')
 
         reference_type = node.attr('data-reference-filter')
-        reference_filter = reference_type.constantize
+        reference_filter = Banzai::Filter.const_get(reference_type)
 
         return if context[:reference_filter] && reference_filter != context[:reference_filter]
 
-        return unless reference_filter.user_can_reference?(current_user, node, context)
+        return if author && !reference_filter.user_can_reference?(author, node, context)
+
+        return unless reference_filter.user_can_see_reference?(current_user, node, context)
 
         references = reference_filter.referenced_by(node)
         return unless references
@@ -47,17 +49,20 @@ module Gitlab
         end
       end
 
-      # Will load all references of one type using one query.
       def load_lazy_references
         refs = result[:references]
         refs.each do |type, values|
-          refs[type] = ReferenceFilter::LazyReference.load(values)
+          refs[type] = ReferenceExtractor.lazily(values)
         end
       end
 
       def current_user
         context[:current_user]
       end
+
+      def author
+        context[:author]
+      end
     end
   end
 end
diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
similarity index 97%
rename from lib/gitlab/markdown/relative_link_filter.rb
rename to lib/banzai/filter/relative_link_filter.rb
index 632be4d754255bbf4803dbe3c6f2e12ace6a4b9a..5a081125f21dcd67e147f237de4d2ffe7e13ea52 100644
--- a/lib/gitlab/markdown/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -1,9 +1,9 @@
-require 'gitlab/markdown'
+require 'banzai'
 require 'html/pipeline/filter'
 require 'uri'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML filter that "fixes" relative links to files in a repository.
     #
     # Context options:
@@ -16,7 +16,7 @@ module Gitlab
       def call
         return doc unless linkable_files?
 
-        doc.search('a').each do |el|
+        doc.search('a:not(.gfm)').each do |el|
           process_link_attr el.attribute('href')
         end
 
diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
similarity index 93%
rename from lib/gitlab/markdown/sanitization_filter.rb
rename to lib/banzai/filter/sanitization_filter.rb
index ffb9dc33b641d7109dae0456272d60c3e853346c..d03e3ae4b3c9996d12823215bb31aa3d30bd5800 100644
--- a/lib/gitlab/markdown/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -1,9 +1,9 @@
-require 'gitlab/markdown'
+require 'banzai'
 require 'html/pipeline/filter'
 require 'html/pipeline/sanitization_filter'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # Sanitize HTML
     #
     # Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
@@ -11,7 +11,7 @@ module Gitlab
       def whitelist
         # Descriptions are more heavily sanitized, allowing only a few elements.
         # See http://git.io/vkuAN
-        if pipeline == :description
+        if context[:inline_sanitization]
           whitelist = LIMITED
           whitelist[:elements] -= %w(pre code img ol ul li)
         else
@@ -25,10 +25,6 @@ module Gitlab
 
       private
 
-      def pipeline
-        context[:pipeline] || :default
-      end
-
       def customized?(transformers)
         transformers.last.source_location[0] == __FILE__
       end
diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/banzai/filter/snippet_reference_filter.rb
similarity index 91%
rename from lib/gitlab/markdown/snippet_reference_filter.rb
rename to lib/banzai/filter/snippet_reference_filter.rb
index f7bd07c2a34f210d0c71bcab78daa02cce11e693..1ad5df96f85c8cb7ca222b91f60793084b48059a 100644
--- a/lib/gitlab/markdown/snippet_reference_filter.rb
+++ b/lib/banzai/filter/snippet_reference_filter.rb
@@ -1,7 +1,7 @@
-require 'gitlab/markdown'
+require 'banzai'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML filter that replaces snippet references with links. References to
     # snippets that do not exist are ignored.
     #
diff --git a/lib/gitlab/markdown/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
similarity index 94%
rename from lib/gitlab/markdown/syntax_highlight_filter.rb
rename to lib/banzai/filter/syntax_highlight_filter.rb
index 8597e02f0dec8c09d9903c084d0d44c660e8b555..c889cc1e97cd3ea62eaffb7715e20da2244ddd9d 100644
--- a/lib/gitlab/markdown/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -1,9 +1,9 @@
-require 'gitlab/markdown'
+require 'banzai'
 require 'html/pipeline/filter'
 require 'rouge/plugins/redcarpet'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML Filter to highlight fenced code blocks
     #
     class SyntaxHighlightFilter < HTML::Pipeline::Filter
diff --git a/lib/gitlab/markdown/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb
similarity index 93%
rename from lib/gitlab/markdown/table_of_contents_filter.rb
rename to lib/banzai/filter/table_of_contents_filter.rb
index bbb3bf7fc8b3e6ddb1c95cf62dda1ff76a298d9a..9b3e67206d5539c49f00d993cb9bdfa9c3ba5cd2 100644
--- a/lib/gitlab/markdown/table_of_contents_filter.rb
+++ b/lib/banzai/filter/table_of_contents_filter.rb
@@ -1,8 +1,8 @@
-require 'gitlab/markdown'
+require 'banzai'
 require 'html/pipeline/filter'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML filter that adds an anchor child element to all Headers in a
     # document, so that they can be linked to.
     #
@@ -31,7 +31,7 @@ module Gitlab
 
           id = text.downcase
           id.gsub!(PUNCTUATION_REGEXP, '') # remove punctuation
-          id.gsub!(' ', '-') # replace spaces with dash
+          id.tr!(' ', '-') # replace spaces with dash
           id.squeeze!('-') # replace multiple dashes with one
 
           uniq = (headers[id] > 0) ? "-#{headers[id]}" : ''
diff --git a/lib/gitlab/markdown/task_list_filter.rb b/lib/banzai/filter/task_list_filter.rb
similarity index 91%
rename from lib/gitlab/markdown/task_list_filter.rb
rename to lib/banzai/filter/task_list_filter.rb
index 2f133ae850060fc0d72e18a3ce0c7a9ad0749f76..bdf7c2ebdfc6c2904372fd72390801a634a55c32 100644
--- a/lib/gitlab/markdown/task_list_filter.rb
+++ b/lib/banzai/filter/task_list_filter.rb
@@ -1,8 +1,8 @@
-require 'gitlab/markdown'
+require 'banzai'
 require 'task_list/filter'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # Work around a bug in the default TaskList::Filter that adds a `task-list`
     # class to every list element, regardless of whether or not it contains a
     # task list.
diff --git a/lib/gitlab/markdown/upload_link_filter.rb b/lib/banzai/filter/upload_link_filter.rb
similarity index 94%
rename from lib/gitlab/markdown/upload_link_filter.rb
rename to lib/banzai/filter/upload_link_filter.rb
index fbada73ab865c9a677863164f8a1b814839fdf13..1a1d0aad8ca6efb35ded2fb9fbb3d292b53833a8 100644
--- a/lib/gitlab/markdown/upload_link_filter.rb
+++ b/lib/banzai/filter/upload_link_filter.rb
@@ -1,9 +1,9 @@
-require 'gitlab/markdown'
+require 'banzai'
 require 'html/pipeline/filter'
 require 'uri'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML filter that "fixes" relative upload links to files.
     # Context options:
     #   :project (required) - Current project
diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
similarity index 70%
rename from lib/gitlab/markdown/user_reference_filter.rb
rename to lib/banzai/filter/user_reference_filter.rb
index ab5e1f6fe9eda11e4794b8fb6db570546b1eca26..964ab60f614b9f3a75ab1039e71a3cd2abf733f9 100644
--- a/lib/gitlab/markdown/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -1,7 +1,7 @@
-require 'gitlab/markdown'
+require 'banzai'
 
-module Gitlab
-  module Markdown
+module Banzai
+  module Filter
     # HTML filter that replaces user or group references with links.
     #
     # A special `@all` reference is also supported.
@@ -39,7 +39,7 @@ module Gitlab
         end
       end
 
-      def self.user_can_reference?(user, node, context)
+      def self.user_can_see_reference?(user, node, context)
         if node.has_attribute?('data-group')
           group = Group.find(node.attr('data-group')) rescue nil
           Ability.abilities.allowed?(user, :read_group, group)
@@ -48,10 +48,26 @@ module Gitlab
         end
       end
 
+      def self.user_can_reference?(user, node, context)
+        # Only team members can reference `@all`
+        if node.has_attribute?('data-project')
+          project = Project.find(node.attr('data-project')) rescue nil
+          return false unless project
+
+          user && project.team.member?(user)
+        else
+          super
+        end
+      end
+
       def call
         replace_text_nodes_matching(User.reference_pattern) do |content|
           user_link_filter(content)
         end
+
+        replace_link_nodes_with_href(User.reference_pattern) do |link, text|
+          user_link_filter(link, link_text: text)
+        end
       end
 
       # Replace `@user` user references in text with links to the referenced
@@ -61,12 +77,12 @@ module Gitlab
       #
       # Returns a String with `@user` references replaced with links. All links
       # have `gfm` and `gfm-project_member` class names attached for styling.
-      def user_link_filter(text)
+      def user_link_filter(text, link_text: nil)
         self.class.references_in(text) do |match, username|
           if username == 'all'
-            link_to_all
+            link_to_all(link_text: link_text)
           elsif namespace = Namespace.find_by(path: username)
-            link_to_namespace(namespace) || match
+            link_to_namespace(namespace, link_text: link_text) || match
           else
             match
           end
@@ -83,42 +99,42 @@ module Gitlab
         reference_class(:project_member)
       end
 
-      def link_to_all
+      def link_to_all(link_text: nil)
         project = context[:project]
         url = urls.namespace_project_url(project.namespace, project,
                                          only_path: context[:only_path])
         data = data_attribute(project: project.id)
-        text = User.reference_prefix + 'all'
+        text = link_text || User.reference_prefix + 'all'
 
         link_tag(url, data, text)
       end
 
-      def link_to_namespace(namespace)
+      def link_to_namespace(namespace, link_text: nil)
         if namespace.is_a?(Group)
-          link_to_group(namespace.path, namespace)
+          link_to_group(namespace.path, namespace, link_text: link_text)
         else
-          link_to_user(namespace.path, namespace)
+          link_to_user(namespace.path, namespace, link_text: link_text)
         end
       end
 
-      def link_to_group(group, namespace)
+      def link_to_group(group, namespace, link_text: nil)
         url = urls.group_url(group, only_path: context[:only_path])
         data = data_attribute(group: namespace.id)
-        text = Group.reference_prefix + group
+        text = link_text || Group.reference_prefix + group
 
         link_tag(url, data, text)
       end
 
-      def link_to_user(user, namespace)
+      def link_to_user(user, namespace, link_text: nil)
         url = urls.user_url(user, only_path: context[:only_path])
         data = data_attribute(user: namespace.owner_id)
-        text = User.reference_prefix + user
+        text = link_text || User.reference_prefix + user
 
         link_tag(url, data, text)
       end
 
       def link_tag(url, data, text)
-        %(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
+        %(<a href="#{url}" #{data} class="#{link_class}">#{escape_once(text)}</a>)
       end
     end
   end
diff --git a/lib/banzai/lazy_reference.rb b/lib/banzai/lazy_reference.rb
new file mode 100644
index 0000000000000000000000000000000000000000..073ec5d9801f0ba466662b82a81afdb0f68f6596
--- /dev/null
+++ b/lib/banzai/lazy_reference.rb
@@ -0,0 +1,27 @@
+require 'banzai'
+
+module Banzai
+  class LazyReference
+    def self.load(refs)
+      lazy_references, values = refs.partition { |ref| ref.is_a?(self) }
+
+      lazy_values = lazy_references.group_by(&:klass).flat_map do |klass, refs|
+        ids = refs.flat_map(&:ids)
+        klass.where(id: ids)
+      end
+
+      values + lazy_values
+    end
+
+    attr_reader :klass, :ids
+
+    def initialize(klass, ids)
+      @klass = klass
+      @ids = Array.wrap(ids).map(&:to_i)
+    end
+
+    def load
+      self.klass.where(id: self.ids)
+    end
+  end
+end
diff --git a/lib/banzai/pipeline.rb b/lib/banzai/pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4e017809d9d3c85d578f1173182485986aee21c2
--- /dev/null
+++ b/lib/banzai/pipeline.rb
@@ -0,0 +1,10 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    def self.[](name)
+      name ||= :full
+      const_get("#{name.to_s.camelize}Pipeline")
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/asciidoc_pipeline.rb b/lib/banzai/pipeline/asciidoc_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5e76a817be512e619a63bff9c70adbe717306d7f
--- /dev/null
+++ b/lib/banzai/pipeline/asciidoc_pipeline.rb
@@ -0,0 +1,13 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    class AsciidocPipeline < BasePipeline
+      def self.filters
+        [
+          Filter::RelativeLinkFilter
+        ]
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/atom_pipeline.rb b/lib/banzai/pipeline/atom_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..957f352aec5f2d149e89db0b09ee041b0b5da759
--- /dev/null
+++ b/lib/banzai/pipeline/atom_pipeline.rb
@@ -0,0 +1,14 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    class AtomPipeline < FullPipeline
+      def self.transform_context(context)
+        super(context).merge(
+          only_path: false,
+          xhtml: true
+        )
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/base_pipeline.rb b/lib/banzai/pipeline/base_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cd30009e5c097e0cc5045750e44f9dc9eef59865
--- /dev/null
+++ b/lib/banzai/pipeline/base_pipeline.rb
@@ -0,0 +1,30 @@
+require 'banzai'
+require 'html/pipeline'
+
+module Banzai
+  module Pipeline
+    class BasePipeline
+      def self.filters
+        []
+      end
+
+      def self.transform_context(context)
+        context
+      end
+
+      def self.html_pipeline
+        @html_pipeline ||= HTML::Pipeline.new(filters)
+      end
+
+      class << self
+        %i(call to_document to_html).each do |meth|
+          define_method(meth) do |text, context|
+            context = transform_context(context)
+
+            html_pipeline.send(meth, text, context)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/combined_pipeline.rb b/lib/banzai/pipeline/combined_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f3bf1809d18dc431be986ffe795a2ebfc46679d0
--- /dev/null
+++ b/lib/banzai/pipeline/combined_pipeline.rb
@@ -0,0 +1,27 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    module CombinedPipeline
+      def self.new(*pipelines)
+        Class.new(BasePipeline) do
+          const_set :PIPELINES, pipelines
+
+          def self.pipelines
+            self::PIPELINES
+          end
+
+          def self.filters
+            pipelines.flat_map(&:filters)
+          end
+
+          def self.transform_context(context)
+            pipelines.reduce(context) do |context, pipeline|
+              pipeline.transform_context(context)
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/description_pipeline.rb b/lib/banzai/pipeline/description_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..94c2cb165a5646d021e5b9b829e7e6b843954d2c
--- /dev/null
+++ b/lib/banzai/pipeline/description_pipeline.rb
@@ -0,0 +1,14 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    class DescriptionPipeline < FullPipeline
+      def self.transform_context(context)
+        super(context).merge(
+          # SanitizationFilter
+          inline_sanitization: true
+        )
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/email_pipeline.rb b/lib/banzai/pipeline/email_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..14356145a357791ebca63db494a4e8fdde183c46
--- /dev/null
+++ b/lib/banzai/pipeline/email_pipeline.rb
@@ -0,0 +1,13 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    class EmailPipeline < FullPipeline
+      def self.transform_context(context)
+        super(context).merge(
+          only_path: false
+        )
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/full_pipeline.rb b/lib/banzai/pipeline/full_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..72395a5d50ef223860877c76706f73a912cbdef1
--- /dev/null
+++ b/lib/banzai/pipeline/full_pipeline.rb
@@ -0,0 +1,9 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    class FullPipeline < CombinedPipeline.new(PlainMarkdownPipeline, GfmPipeline)
+
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..38750b55ec7115f4d91f24d2bc83933093d08370
--- /dev/null
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -0,0 +1,41 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    class GfmPipeline < BasePipeline
+      def self.filters
+        @filters ||= [
+          Filter::SyntaxHighlightFilter,
+          Filter::SanitizationFilter,
+
+          Filter::UploadLinkFilter,
+          Filter::EmojiFilter,
+          Filter::TableOfContentsFilter,
+          Filter::AutolinkFilter,
+          Filter::ExternalLinkFilter,
+
+          Filter::UserReferenceFilter,
+          Filter::IssueReferenceFilter,
+          Filter::ExternalIssueReferenceFilter,
+          Filter::MergeRequestReferenceFilter,
+          Filter::SnippetReferenceFilter,
+          Filter::CommitRangeReferenceFilter,
+          Filter::CommitReferenceFilter,
+          Filter::LabelReferenceFilter,
+
+          Filter::TaskListFilter
+        ]
+      end
+
+      def self.transform_context(context)
+        context.merge(
+          only_path: true,
+
+          # EmojiFilter
+          asset_host: Gitlab::Application.config.asset_host,
+          asset_root: Gitlab.config.gitlab.base_url
+        )
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/note_pipeline.rb b/lib/banzai/pipeline/note_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..893351438525c4ec7de8c894e428d134e92f9bcd
--- /dev/null
+++ b/lib/banzai/pipeline/note_pipeline.rb
@@ -0,0 +1,14 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    class NotePipeline < FullPipeline
+      def self.transform_context(context)
+        super(context).merge(
+          # TableOfContentsFilter
+          no_header_anchors: true
+        )
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/plain_markdown_pipeline.rb b/lib/banzai/pipeline/plain_markdown_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..998fd75daa2d57b1efbd9b8d6cfa78e3bc10f7bd
--- /dev/null
+++ b/lib/banzai/pipeline/plain_markdown_pipeline.rb
@@ -0,0 +1,13 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    class PlainMarkdownPipeline < BasePipeline
+      def self.filters
+        [
+          Filter::MarkdownFilter
+        ]
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..148f24b6ce1d0c29259f13b4bb6f0609e7200e8f
--- /dev/null
+++ b/lib/banzai/pipeline/post_process_pipeline.rb
@@ -0,0 +1,20 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    class PostProcessPipeline < BasePipeline
+      def self.filters
+        [
+          Filter::RelativeLinkFilter,
+          Filter::RedactorFilter
+        ]
+      end
+
+      def self.transform_context(context)
+        context.merge(
+          post_process: true
+        )
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/reference_extraction_pipeline.rb b/lib/banzai/pipeline/reference_extraction_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4f9bc9fcccc501fb5d3386358fe13934047c331c
--- /dev/null
+++ b/lib/banzai/pipeline/reference_extraction_pipeline.rb
@@ -0,0 +1,13 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    class ReferenceExtractionPipeline < BasePipeline
+      def self.filters
+        [
+          Filter::ReferenceGathererFilter
+        ]
+      end
+    end
+  end
+end
diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6725c9039a9ff0ecca91610adc4302eb02a84c15
--- /dev/null
+++ b/lib/banzai/pipeline/single_line_pipeline.rb
@@ -0,0 +1,9 @@
+require 'banzai'
+
+module Banzai
+  module Pipeline
+    class SingleLinePipeline < GfmPipeline
+
+    end
+  end
+end
diff --git a/lib/banzai/reference_extractor.rb b/lib/banzai/reference_extractor.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2c197d31898cb39b21f695de15c4d6daee91a6a0
--- /dev/null
+++ b/lib/banzai/reference_extractor.rb
@@ -0,0 +1,55 @@
+require 'banzai'
+
+module Banzai
+  # Extract possible GFM references from an arbitrary String for further processing.
+  class ReferenceExtractor
+    class << self
+      LAZY_KEY = :banzai_reference_extractor_lazy
+
+      def lazy?
+        Thread.current[LAZY_KEY]
+      end
+
+      def lazily(values = nil, &block)
+        return (values || block.call).uniq if lazy?
+
+        begin
+          Thread.current[LAZY_KEY] = true
+
+          values ||= block.call
+
+          Banzai::LazyReference.load(values.uniq).uniq
+        ensure
+          Thread.current[LAZY_KEY] = false
+        end
+      end
+    end
+
+    def initialize
+      @texts = []
+    end
+
+    def analyze(text, context = {})
+      @texts << Renderer.render(text, context)
+    end
+
+    def references(type, context = {})
+      filter = Banzai::Filter["#{type}_reference"]
+
+      context.merge!(
+        pipeline: :reference_extraction,
+
+        # ReferenceGathererFilter
+        reference_filter: filter
+      )
+
+      self.class.lazily do
+        @texts.flat_map do |html|
+          text_context = context.dup
+          result = Renderer.render_result(html, text_context)
+          result[:references][type]
+        end.uniq
+      end
+    end
+  end
+end
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..115ae91452486b304779a1f1d342ab16124f99e2
--- /dev/null
+++ b/lib/banzai/renderer.rb
@@ -0,0 +1,78 @@
+module Banzai
+  module Renderer
+    CACHE_ENABLED = false
+
+    # Convert a Markdown String into an HTML-safe String of HTML
+    #
+    # Note that while the returned HTML will have been sanitized of dangerous
+    # HTML, it may post a risk of information leakage if it's not also passed
+    # through `post_process`.
+    #
+    # Also note that the returned String is always HTML, not XHTML. Views
+    # requiring XHTML, such as Atom feeds, need to call `post_process` on the
+    # result, providing the appropriate `pipeline` option.
+    #
+    # markdown - Markdown String
+    # context  - Hash of context options passed to our HTML Pipeline
+    #
+    # Returns an HTML-safe String
+    def self.render(text, context = {})
+      cache_key = context.delete(:cache_key)
+      cache_key = full_cache_key(cache_key, context[:pipeline])
+
+      if cache_key && CACHE_ENABLED
+        Rails.cache.fetch(cache_key) do
+          cacheless_render(text, context)
+        end
+      else
+        cacheless_render(text, context)
+      end
+    end
+
+    def self.render_result(text, context = {})
+      Pipeline[context[:pipeline]].call(text, context)
+    end
+
+    # Perform post-processing on an HTML String
+    #
+    # This method is used to perform state-dependent changes to a String of
+    # HTML, such as removing references that the current user doesn't have
+    # permission to make (`RedactorFilter`).
+    #
+    # html     - String to process
+    # context  - Hash of options to customize output
+    #            :pipeline  - Symbol pipeline type
+    #            :project   - Project
+    #            :user      - User object
+    #
+    # Returns an HTML-safe String
+    def self.post_process(html, context)
+      context = Pipeline[context[:pipeline]].transform_context(context)
+
+      pipeline = Pipeline[:post_process]
+      if context[:xhtml]
+        pipeline.to_document(html, context).to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML)
+      else
+        pipeline.to_html(html, context)
+      end.html_safe
+    end
+
+    private
+
+    def self.cacheless_render(text, context = {})
+      result = render_result(text, context)
+
+      output = result[:output]
+      if output.respond_to?(:to_html)
+        output.to_html
+      else
+        output.to_s
+      end
+    end
+
+    def self.full_cache_key(cache_key, pipeline_name)
+      return unless cache_key
+      ["banzai", *cache_key, pipeline_name || :full]
+    end
+  end
+end
diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb
index 07e68216d7f1d08876f09545cd426bac8f787323..5c347e432b4e98dea738a1f495608826edd0bf06 100644
--- a/lib/ci/api/api.rb
+++ b/lib/ci/api/api.rb
@@ -30,9 +30,7 @@ module Ci
       helpers Gitlab::CurrentSettings
 
       mount Builds
-      mount Commits
       mount Runners
-      mount Projects
       mount Triggers
     end
   end
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 0a58667280745de66b4fc6f89488d55fa796516d..15faa6edd8402c2ca4a11a8c7493db453153f1b3 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -58,6 +58,7 @@ module Ci
         #   POST /builds/:id/artifacts/authorize
         post ":id/artifacts/authorize" do
           require_gitlab_workhorse!
+          not_allowed! unless Gitlab.config.artifacts.enabled
           build = Ci::Build.find_by_id(params[:id])
           not_found! unless build
           authenticate_build_token!(build)
@@ -91,6 +92,7 @@ module Ci
         #   POST /builds/:id/artifacts
         post ":id/artifacts" do
           require_gitlab_workhorse!
+          not_allowed! unless Gitlab.config.artifacts.enabled
           build = Ci::Build.find_by_id(params[:id])
           not_found! unless build
           authenticate_build_token!(build)
diff --git a/lib/ci/api/commits.rb b/lib/ci/api/commits.rb
deleted file mode 100644
index a60769d83059f431585697a4137c2d721febb227..0000000000000000000000000000000000000000
--- a/lib/ci/api/commits.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-module Ci
-  module API
-    class Commits < Grape::API
-      resource :commits do
-        # Get list of commits per project
-        #
-        # Parameters:
-        #   project_id (required) - The ID of a project
-        #   project_token (requires) - Project token
-        #   page (optional)
-        #   per_page (optional) - items per request (default is 20)
-        #
-        get do
-          required_attributes! [:project_id, :project_token]
-          project = Ci::Project.find(params[:project_id])
-          authenticate_project_token!(project)
-
-          commits = project.commits.page(params[:page]).per(params[:per_page] || 20)
-          present commits, with: Entities::CommitWithBuilds
-        end
-
-        # Create a commit
-        #
-        # Parameters:
-        #   project_id (required) - The ID of a project
-        #   project_token (requires) - Project token
-        #   data (required) - GitLab push data
-        #
-        #   Sample GitLab push data:
-        #   {
-        #     "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
-        #     "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
-        #     "ref": "refs/heads/master",
-        #     "commits": [
-        #       {
-        #         "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
-        #         "message": "Update Catalan translation to e38cb41.",
-        #         "timestamp": "2011-12-12T14:27:31+02:00",
-        #         "url": "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
-        #         "author": {
-        #           "name": "Jordi Mallach",
-        #           "email": "jordi@softcatala.org",
-        #         }
-        #       }, .... more commits
-        #     ]
-        #   }
-        #
-        # Example Request:
-        #   POST /commits
-        post do
-          required_attributes! [:project_id, :data, :project_token]
-          project = Ci::Project.find(params[:project_id])
-          authenticate_project_token!(project)
-          commit = Ci::CreateCommitService.new.execute(project, current_user, params[:data])
-
-          if commit.persisted?
-            present commit, with: Entities::CommitWithBuilds
-          else
-            errors = commit.errors.full_messages.join(", ")
-            render_api_error!(errors, 400)
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb
index 750f421872deea8e4c07788523d541e118e17095..e4ac0545ea2f5f707f07adfe2f14ecb0eea5f6d8 100644
--- a/lib/ci/api/entities.rb
+++ b/lib/ci/api/entities.rb
@@ -37,15 +37,6 @@ module Ci
         expose :id, :token
       end
 
-      class Project < Grape::Entity
-        expose :id, :name, :token, :default_ref, :gitlab_url, :path,
-          :always_build, :polling_interval, :public, :ssh_url_to_repo, :gitlab_id
-
-        expose :timeout do |model|
-          model.timeout
-        end
-      end
-
       class RunnerProject < Grape::Entity
         expose :id, :project_id, :runner_id
       end
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 02502333756350c33372e1eddca17c6679a3e011..1c91204e98ca1c9331f0c597d4e8bb5933b3c7cd 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -6,22 +6,22 @@ module Ci
       UPDATE_RUNNER_EVERY = 60
 
       def authenticate_runners!
-        forbidden! unless params[:token] == GitlabCi::REGISTRATION_TOKEN
+        forbidden! unless runner_registration_token_valid?
       end
 
       def authenticate_runner!
         forbidden! unless current_runner
       end
 
-      def authenticate_project_token!(project)
-        forbidden! unless project.valid_token?(params[:project_token])
-      end
-
       def authenticate_build_token!(build)
         token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s
         forbidden! unless token && build.valid_token?(token)
       end
 
+      def runner_registration_token_valid?
+        params[:token] == current_application_settings.runners_registration_token
+      end
+
       def update_runner_last_contact
         # Use a random threshold to prevent beating DB updates
         contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY)
diff --git a/lib/ci/api/projects.rb b/lib/ci/api/projects.rb
deleted file mode 100644
index d719ad9e8d592f8dfb85084b97ad850e2fbe567d..0000000000000000000000000000000000000000
--- a/lib/ci/api/projects.rb
+++ /dev/null
@@ -1,195 +0,0 @@
-module Ci
-  module API
-    # Projects API
-    class Projects < Grape::API
-      before { authenticate! }
-
-      resource :projects do
-        # Register new webhook for project
-        #
-        # Parameters
-        #   project_id (required) - The ID of a project
-        #   web_hook (required) - WebHook URL
-        # Example Request
-        #   POST /projects/:project_id/webhooks
-        post ":project_id/webhooks" do
-          required_attributes! [:web_hook]
-
-          project = Ci::Project.find(params[:project_id])
-
-          unauthorized! unless can?(current_user, :admin_project, project.gl_project)
-
-          web_hook = project.web_hooks.new({ url: params[:web_hook] })
-
-          if web_hook.save
-            present web_hook, with: Entities::WebHook
-          else
-            errors = web_hook.errors.full_messages.join(", ")
-            render_api_error!(errors, 400)
-          end
-        end
-
-        # Retrieve all Gitlab CI projects that the user has access to
-        #
-        # Example Request:
-        #   GET /projects
-        get do
-          gitlab_projects = current_user.authorized_projects
-          gitlab_projects = filter_projects(gitlab_projects)
-          gitlab_projects = paginate gitlab_projects
-
-          ids = gitlab_projects.map { |project| project.id }
-
-          projects = Ci::Project.where("gitlab_id IN (?)", ids).load
-          present projects, with: Entities::Project
-        end
-
-        # Retrieve all Gitlab CI projects that the user owns
-        #
-        # Example Request:
-        #   GET /projects/owned
-        get "owned" do
-          gitlab_projects = current_user.owned_projects
-          gitlab_projects = filter_projects(gitlab_projects)
-          gitlab_projects = paginate gitlab_projects
-
-          ids = gitlab_projects.map { |project| project.id }
-
-          projects = Ci::Project.where("gitlab_id IN (?)", ids).load
-          present projects, with: Entities::Project
-        end
-
-        # Retrieve info for a Gitlab CI project
-        #
-        # Parameters:
-        #   id (required) - The ID of a project
-        # Example Request:
-        #   GET /projects/:id
-        get ":id" do
-          project = Ci::Project.find(params[:id])
-          unauthorized! unless can?(current_user, :read_project, project.gl_project)
-
-          present project, with: Entities::Project
-        end
-
-        # Create Gitlab CI project using Gitlab project info
-        #
-        # Parameters:
-        #   gitlab_id (required)       - The gitlab id of the project
-        #   default_ref                - The branch to run against (defaults to `master`)
-        # Example Request:
-        #   POST /projects
-        post do
-          required_attributes! [:gitlab_id]
-
-          filtered_params = {
-            gitlab_id:       params[:gitlab_id],
-            # we accept gitlab_url for backward compatibility for a while (added to 7.11)
-            default_ref:     params[:default_ref] || 'master'
-          }
-
-          project = Ci::Project.new(filtered_params)
-          project.build_missing_services
-
-          if project.save
-            present project, with: Entities::Project
-          else
-            errors = project.errors.full_messages.join(", ")
-            render_api_error!(errors, 400)
-          end
-        end
-
-        # Update a Gitlab CI project
-        #
-        # Parameters:
-        #   id (required)   - The ID of a project
-        #   default_ref      - The branch to run against (defaults to `master`)
-        # Example Request:
-        #   PUT /projects/:id
-        put ":id" do
-          project = Ci::Project.find(params[:id])
-
-          unauthorized! unless can?(current_user, :admin_project, project.gl_project)
-
-          attrs = attributes_for_keys [:default_ref]
-
-          if project.update_attributes(attrs)
-            present project, with: Entities::Project
-          else
-            errors = project.errors.full_messages.join(", ")
-            render_api_error!(errors, 400)
-          end
-        end
-
-        # Remove a Gitlab CI project
-        #
-        # Parameters:
-        #   id (required) - The ID of a project
-        # Example Request:
-        #   DELETE /projects/:id
-        delete ":id" do
-          project = Ci::Project.find(params[:id])
-
-          unauthorized! unless can?(current_user, :admin_project, project.gl_project)
-
-          project.destroy
-        end
-
-        # Link a Gitlab CI project to a runner
-        #
-        # Parameters:
-        #   id (required) - The ID of a CI project
-        #   runner_id (required) - The ID of a runner
-        # Example Request:
-        #   POST /projects/:id/runners/:runner_id
-        post ":id/runners/:runner_id" do
-          project = Ci::Project.find(params[:id])
-          runner  = Ci::Runner.find(params[:runner_id])
-
-          unauthorized! unless can?(current_user, :admin_project, project.gl_project)
-
-          options = {
-            project_id: project.id,
-            runner_id:  runner.id
-          }
-
-          runner_project = Ci::RunnerProject.new(options)
-
-          if runner_project.save
-            present runner_project, with: Entities::RunnerProject
-          else
-            errors = project.errors.full_messages.join(", ")
-            render_api_error!(errors, 400)
-          end
-        end
-
-        # Remove a Gitlab CI project from a runner
-        #
-        # Parameters:
-        #   id (required) - The ID of a CI project
-        #   runner_id (required) - The ID of a runner
-        # Example Request:
-        #   DELETE /projects/:id/runners/:runner_id
-        delete ":id/runners/:runner_id" do
-          project = Ci::Project.find(params[:id])
-          runner  = Ci::Runner.find(params[:runner_id])
-
-          unauthorized! unless can?(current_user, :admin_project, project.gl_project)
-
-          options = {
-            project_id: project.id,
-            runner_id:  runner.id
-          }
-
-          runner_project = Ci::RunnerProject.find_by(options)
-
-          if runner_project.present?
-            runner_project.destroy
-          else
-            not_found!
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb
index 1466fe4356e81085c234374ff4440a9c7a033965..bfc14fe7a6b5ac3f7ced3589b4d9ec7adcd5c127 100644
--- a/lib/ci/api/runners.rb
+++ b/lib/ci/api/runners.rb
@@ -3,17 +3,6 @@ module Ci
     # Runners API
     class Runners < Grape::API
       resource :runners do
-        # Get list of all available runners
-        #
-        # Example Request:
-        #   GET /runners
-        get do
-          authenticate!
-          runners = Ci::Runner.all
-
-          present runners, with: Entities::Runner
-        end
-
         # Delete runner
         # Parameters:
         #   token (required) - The unique token of runner
@@ -40,14 +29,14 @@ module Ci
           required_attributes! [:token]
 
           runner =
-            if params[:token] == GitlabCi::REGISTRATION_TOKEN
+            if runner_registration_token_valid?
               # Create shared runner. Requires admin access
               Ci::Runner.create(
                 description: params[:description],
                 tag_list: params[:tag_list],
                 is_shared: true
               )
-            elsif project = Ci::Project.find_by(token: params[:token])
+            elsif project = Project.find_by(runners_token: params[:token])
               # Create a specific runner for project.
               project.runners.create(
                 description: params[:description],
diff --git a/lib/ci/api/triggers.rb b/lib/ci/api/triggers.rb
index 40907d6db541ac6c260781298d622929cc096ff6..63b42113513493dcac31c97d7f17464ca4e2c895 100644
--- a/lib/ci/api/triggers.rb
+++ b/lib/ci/api/triggers.rb
@@ -14,7 +14,7 @@ module Ci
         post ":id/refs/:ref/trigger" do
           required_attributes! [:token]
 
-          project = Ci::Project.find(params[:id])
+          project = Project.find_by(ci_id: params[:id].to_i)
           trigger = Ci::Trigger.find_by_token(params[:token].to_s)
           not_found! unless project && trigger
           unauthorized! unless trigger.project == project
diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb
index 5ff7407c6fe7775244b9bf85d8c0e59c47e234c4..d53bdcbd0f22154ef56923ebc05b5b2cd070b732 100644
--- a/lib/ci/charts.rb
+++ b/lib/ci/charts.rb
@@ -60,7 +60,7 @@ module Ci
 
     class BuildTime < Chart
       def collect
-        commits = project.commits.last(30)
+        commits = project.ci_commits.last(30)
 
         commits.each do |commit|
           @labels << commit.short_sha
diff --git a/lib/ci/current_settings.rb b/lib/ci/current_settings.rb
deleted file mode 100644
index fd78b0249706688fbd378e665e756647864c5b19..0000000000000000000000000000000000000000
--- a/lib/ci/current_settings.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-module Ci
-  module CurrentSettings
-    def current_application_settings
-      key = :ci_current_application_settings
-
-      RequestStore.store[key] ||= begin
-        if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('ci_application_settings')
-          Ci::ApplicationSetting.current || Ci::ApplicationSetting.create_from_defaults
-        else
-          fake_application_settings
-        end
-      end
-    end
-
-    def fake_application_settings
-      OpenStruct.new(
-        all_broken_builds: Ci::Settings.gitlab_ci['all_broken_builds'],
-        add_pusher: Ci::Settings.gitlab_ci['add_pusher'],
-      )
-    end
-  end
-end
diff --git a/lib/ci/git.rb b/lib/ci/git.rb
deleted file mode 100644
index 7acc3f38edbe76994177a0679bada85421215966..0000000000000000000000000000000000000000
--- a/lib/ci/git.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Ci
-  module Git
-    BLANK_SHA = '0' * 40
-  end
-end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 3beafcad117f19cbaafa13f432c632d1b65ed894..bcdfd38d292f245b4fe4df8ed7dc93fb4a67dc57 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -10,7 +10,7 @@ module Ci
     attr_reader :before_script, :image, :services, :variables, :path, :cache
 
     def initialize(config, path = nil)
-      @config = YAML.load(config)
+      @config = YAML.safe_load(config, [Symbol])
       @path = path
 
       unless @config.is_a? Hash
@@ -132,26 +132,36 @@ module Ci
     end
 
     def validate_job!(name, job)
+      validate_job_name!(name)
+      validate_job_keys!(name, job)
+      validate_job_types!(name, job)
+
+      validate_job_stage!(name, job) if job[:stage]
+      validate_job_cache!(name, job) if job[:cache]
+      validate_job_artifacts!(name, job) if job[:artifacts]
+    end
+
+    private
+
+    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
       end
+    end
 
+    def validate_job_types!(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[:stage]
-        unless job[:stage].is_a?(String) && job[:stage].in?(stages)
-          raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}"
-        end
-      end
-
       if job[:image] && !validate_string(job[:image])
         raise ValidationError, "#{name} job: image should be a string"
       end
@@ -172,36 +182,40 @@ module Ci
         raise ValidationError, "#{name} job: except parameter should be an array of strings"
       end
 
-      if job[:cache]
-        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
+      if job[:allow_failure] && !validate_boolean(job[:allow_failure])
+        raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
       end
 
-      if job[:artifacts]
-        if job[:artifacts][:untracked] && !validate_boolean(job[:artifacts][:untracked])
-          raise ValidationError, "#{name} job: artifacts:untracked 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
+    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
+    def validate_job_stage!(name, job)
+      unless job[:stage].is_a?(String) && job[:stage].in?(stages)
+        raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}"
       end
+    end
 
-      if job[:allow_failure] && !validate_boolean(job[:allow_failure])
-        raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
+    def validate_job_cache!(name, job)
+      if job[:cache][:untracked] && !validate_boolean(job[:cache][:untracked])
+        raise ValidationError, "#{name} job: cache:untracked 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"
+      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
 
-    private
+    def validate_job_artifacts!(name, job)
+      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
+    end
 
     def validate_array_of_strings(values)
       values.is_a?(Array) && values.all? { |value| validate_string(value) }
diff --git a/lib/ci/scheduler.rb b/lib/ci/scheduler.rb
deleted file mode 100644
index ee0958f4be14cc03708385a6cba4aa49e0cb9309..0000000000000000000000000000000000000000
--- a/lib/ci/scheduler.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module Ci
-  class Scheduler
-    def perform
-      projects = Ci::Project.where(always_build: true).all
-      projects.each do |project|
-        last_commit = project.commits.last
-        next unless last_commit && last_commit.last_build
-
-        interval = project.polling_interval
-        if (last_commit.last_build.created_at + interval.hours) < Time.now
-          last_commit.retry
-        end
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index bf33e5b1b1e4526051dce64ad584c67bf2e43203..b203b9d70e4a149277dfa30096391d60746a7f85 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -1,14 +1,10 @@
 require 'asciidoctor'
-require 'html/pipeline'
 
 module Gitlab
   # Parser/renderer for the AsciiDoc format that uses Asciidoctor and filters
   # the resulting HTML through HTML pipeline filters.
   module Asciidoc
 
-    # Provide autoload paths for filters to prevent a circular dependency error
-    autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
-
     DEFAULT_ADOC_ATTRS = [
       'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab',
       'env-gitlab', 'source-highlighter=html-pipeline'
@@ -24,13 +20,11 @@ module Gitlab
     #                 :requested_path
     #                 :ref
     # asciidoc_opts - a Hash of options to pass to the Asciidoctor converter
-    # html_opts     - a Hash of options for HTML output:
-    #                 :xhtml - output XHTML instead of HTML
     #
-    def self.render(input, context, asciidoc_opts = {}, html_opts = {})
-      asciidoc_opts = asciidoc_opts.reverse_merge(
+    def self.render(input, context, asciidoc_opts = {})
+      asciidoc_opts.reverse_merge!(
         safe: :secure,
-        backend: html_opts[:xhtml] ? :xhtml5 : :html5,
+        backend: :html5,
         attributes: []
       )
       asciidoc_opts[:attributes].unshift(*DEFAULT_ADOC_ATTRS)
@@ -38,23 +32,10 @@ module Gitlab
       html = ::Asciidoctor.convert(input, asciidoc_opts)
 
       if context[:project]
-        result = HTML::Pipeline.new(filters).call(html, context)
-
-        save_opts = html_opts[:xhtml] ?
-          Nokogiri::XML::Node::SaveOptions::AS_XHTML : 0
-
-        html = result[:output].to_html(save_with: save_opts)
+        html = Banzai.render(html, context.merge(pipeline: :asciidoc))
       end
 
       html.html_safe
     end
-
-    private
-
-    def self.filters
-      [
-        Gitlab::Markdown::RelativeLinkFilter
-      ]
-    end
   end
 end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 0d156047ff09f7318542db1aaf44525172152cef..cdcaae8094cc0199bdb919c0d83c558726a08006 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -77,7 +77,9 @@ module Grack
       if project && matched_login.present? && git_cmd == 'git-upload-pack'
         underscored_service = matched_login['s'].underscore
 
-        if Service.available_services_names.include?(underscored_service)
+        if underscored_service == 'gitlab_ci'
+          return project && project.valid_build_token?(password)
+        elsif Service.available_services_names.include?(underscored_service)
           service_method = "#{underscored_service}_service"
           service = project.send(service_method)
 
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 87ac30b5ffef959e7baa2a60f8e28d41310099ed..459e3d6bcdbf1e6c6235ae5a59d4128dce3011a1 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -2,7 +2,7 @@ module Gitlab
   class Shell
     class Error < StandardError; end
 
-    class KeyAdder < Struct.new(:io)
+    KeyAdder = Struct.new(:io) do
       def add_key(id, key)
         key.gsub!(/[[:space:]]+/, ' ').strip!
         io.puts("#{id}\t#{key}")
diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb
index 35e34d033e0ccc8a31b501f8412b5a58b0231917..03aac1a025a8d14b244a62b15cec763e56d16fb6 100644
--- a/lib/gitlab/bitbucket_import/project_creator.rb
+++ b/lib/gitlab/bitbucket_import/project_creator.rb
@@ -11,7 +11,8 @@ module Gitlab
       end
 
       def execute
-        project = ::Projects::CreateService.new(current_user,
+        project = ::Projects::CreateService.new(
+          current_user,
           name: repo["name"],
           path: repo["slug"],
           description: repo["description"],
diff --git a/lib/gitlab/blacklist.rb b/lib/gitlab/blacklist.rb
deleted file mode 100644
index 43145e0ee1b7f2a96b45840678dc1485a47acd1b..0000000000000000000000000000000000000000
--- a/lib/gitlab/blacklist.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Gitlab
-  module Blacklist
-    extend self
-
-    def path
-      %w(
-        admin
-        dashboard
-        files
-        groups
-        help
-        profile
-        projects
-        search
-        public
-        assets
-        u
-        s
-        teams
-        merge_requests
-        issues
-        users
-        snippets
-        services
-        repository
-        hooks
-        notes
-        unsubscribes
-        all
-        ci
-      )
-    end
-  end
-end
diff --git a/lib/gitlab/build_data_builder.rb b/lib/gitlab/build_data_builder.rb
new file mode 100644
index 0000000000000000000000000000000000000000..86bfa0a437805edae5c3452fa13580a3a3f31084
--- /dev/null
+++ b/lib/gitlab/build_data_builder.rb
@@ -0,0 +1,64 @@
+module Gitlab
+  class BuildDataBuilder
+    class << self
+      def build(build)
+        project = build.project
+        commit = build.commit
+        user = build.user
+
+        data = {
+          object_kind: 'build',
+
+          ref: build.ref,
+          tag: build.tag,
+          before_sha: build.before_sha,
+          sha: build.sha,
+
+          # TODO: should this be not prefixed with build_?
+          # Leaving this way to have backward compatibility
+          build_id: build.id,
+          build_name: build.name,
+          build_stage: build.stage,
+          build_status: build.status,
+          build_started_at: build.started_at,
+          build_finished_at: build.finished_at,
+          build_duration: build.duration,
+
+          # TODO: do we still need it?
+          project_id: project.id,
+          project_name: project.name_with_namespace,
+
+          user: {
+            id: user.try(:id),
+            name: user.try(:name),
+            email: user.try(:email),
+          },
+
+          commit: {
+            id: commit.id,
+            sha: commit.sha,
+            message: commit.git_commit_message,
+            author_name: commit.git_author_name,
+            author_email: commit.git_author_email,
+            status: commit.status,
+            duration: commit.duration,
+            started_at: commit.started_at,
+            finished_at: commit.finished_at,
+          },
+
+          repository: {
+            name: project.name,
+            url: project.url_to_repo,
+            description: project.description,
+            homepage: project.web_url,
+            git_http_url: project.http_url_to_repo,
+            git_ssh_url: project.ssh_url_to_repo,
+            visibility_level: project.visibility_level
+          },
+        }
+
+        data
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb
index aeec595782c53fb012dcfc3fa00e67d37db27861..9bef9037ad6dda28ed0480d3a75d7afde0840c9e 100644
--- a/lib/gitlab/closing_issue_extractor.rb
+++ b/lib/gitlab/closing_issue_extractor.rb
@@ -1,6 +1,12 @@
 module Gitlab
   class ClosingIssueExtractor
-    ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern)
+    ISSUE_CLOSING_REGEX = begin
+      link_pattern = URI.regexp(%w(http https))
+
+      pattern = Gitlab.config.gitlab.issue_closing_pattern
+      pattern = pattern.sub('%{issue_ref}', "(?:(?:#{link_pattern})|(?:#{Issue.reference_pattern}))")
+      Regexp.new(pattern).freeze
+    end
 
     def initialize(project, current_user = nil)
       @extractor = Gitlab::ReferenceExtractor.new(project, current_user)
@@ -9,10 +15,12 @@ module Gitlab
     def closed_by_message(message)
       return [] if message.nil?
 
-      closing_statements = message.scan(ISSUE_CLOSING_REGEX).
-        map { |ref| ref[0] }.join(" ")
+      closing_statements = []
+      message.scan(ISSUE_CLOSING_REGEX) do
+        closing_statements << Regexp.last_match[0]
+      end
 
-      @extractor.analyze(closing_statements)
+      @extractor.analyze(closing_statements.join(" "))
 
       @extractor.issues
     end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 2d3e32d95396111ba51b887bf49c504090f5d056..46a4ef0e31febfd300cbc35bc5abb88291e59f27 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -25,7 +25,7 @@ module Gitlab
         session_expire_delay: Settings.gitlab['session_expire_delay'],
         import_sources: Settings.gitlab['import_sources'],
         shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
-        max_artifacts_size: Ci::Settings.gitlab_ci['max_artifacts_size'],
+        max_artifacts_size: Settings.artifacts['max_size'],
       )
     end
 
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 71f37f1fef8b27d5092792d7bd9b77b65541c9be..de77a6fbff1c54c0780f7bddbf75d76061dcfc12 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -7,5 +7,23 @@ module Gitlab
     def self.postgresql?
       ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql'
     end
+
+    def true_value
+      case ActiveRecord::Base.connection.adapter_name.downcase
+      when 'postgresql'
+        "'t'"
+      else
+        1
+      end
+    end
+
+    def false_value
+      case ActiveRecord::Base.connection.adapter_name.downcase
+      when 'postgresql'
+        "'f'"
+      else
+        0
+      end
+    end
   end
 end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index 142058aa69d46e0b1429c5e0b0b8f18ae09cfb16..79061cd014181d0375b622431ae41c5379f493c6 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -46,11 +46,11 @@ module Gitlab
       end
 
       def added_lines
-        diff_lines.select(&:added?).size
+        diff_lines.count(&:added?)
       end
 
       def removed_lines
-        diff_lines.select(&:removed?).size
+        diff_lines.count(&:removed?)
       end
     end
   end
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a2eb7a70bd2a8eb97e8931c5bdb68ab877967e22
--- /dev/null
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -0,0 +1,137 @@
+module Gitlab
+  module Email
+    module Message
+      class RepositoryPush
+        attr_accessor :recipient
+        attr_reader :author_id, :ref, :action
+
+        include Gitlab::Application.routes.url_helpers
+
+        delegate :namespace, :name_with_namespace, to: :project, prefix: :project
+        delegate :name, to: :author, prefix: :author
+
+        def initialize(notify, project_id, recipient, opts = {})
+          raise ArgumentError, 'Missing options: author_id, ref, action' unless
+            opts[:author_id] && opts[:ref] && opts[:action]
+
+          @notify = notify
+          @project_id = project_id
+          @recipient = recipient
+          @opts = opts.dup
+
+          @author_id = @opts.delete(:author_id)
+          @ref = @opts.delete(:ref)
+          @action = @opts.delete(:action)
+        end
+
+        def project
+          @project ||= Project.find(@project_id)
+        end
+
+        def author
+          @author ||= User.find(@author_id)
+        end
+
+        def commits
+          @commits ||= (Commit.decorate(compare.commits, project) if compare)
+        end
+
+        def diffs
+          @diffs ||= (compare.diffs if compare)
+        end
+
+        def diffs_count
+          diffs.count if diffs
+        end
+
+        def compare
+          @opts[:compare]
+        end
+
+        def compare_timeout
+          compare.timeout if compare
+        end
+
+        def reverse_compare?
+          @opts[:reverse_compare] || false
+        end
+
+        def disable_diffs?
+          @opts[:disable_diffs] || false
+        end
+
+        def send_from_committer_email?
+          @opts[:send_from_committer_email] || false
+        end
+
+        def action_name
+          @action_name ||=
+            case @action
+            when :create
+              'pushed new'
+            when :delete
+              'deleted'
+            else
+              'pushed to'
+            end
+        end
+
+        def ref_name
+          @ref_name ||= Gitlab::Git.ref_name(@ref)
+        end
+
+        def ref_type
+          @ref_type ||= Gitlab::Git.tag_ref?(@ref) ? 'tag' : 'branch'
+        end
+
+        def target_url
+          if @action == :push && commits
+            if commits.length > 1
+              namespace_project_compare_url(project_namespace,
+                                            project,
+                                            from: Commit.new(compare.base, project),
+                                            to:   Commit.new(compare.head, project))
+            else
+              namespace_project_commit_url(project_namespace,
+                                           project, commits.first)
+            end
+          else
+            unless @action == :delete
+              namespace_project_tree_url(project_namespace,
+                                         project, ref_name)
+            end
+          end
+        end
+
+        def reply_to
+          if send_from_committer_email? && @notify.can_send_from_user_email?(author)
+            author.email
+          else
+            Gitlab.config.gitlab.email_reply_to
+          end
+        end
+
+        def subject
+          subject_text = '[Git]'
+          subject_text << "[#{project.path_with_namespace}]"
+          subject_text << "[#{ref_name}]" if @action == :push
+          subject_text << ' '
+
+          if @action == :push && commits
+            if commits.length > 1
+              subject_text << "Deleted " if reverse_compare?
+              subject_text << "#{commits.length} commits: #{commits.first.title}"
+            else
+              subject_text << "Deleted 1 commit: " if reverse_compare?
+              subject_text << commits.first.title
+            end
+          else
+            subject_action = action_name.dup
+            subject_action[0] = subject_action[0].capitalize
+            subject_text << "#{subject_action} #{ref_type} #{ref_name}"
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb
index 496256700b87e6059ef37a943f8f8e595f553b79..403ebeec47417d678a0a617fa6697ec89ba0953a 100644
--- a/lib/gitlab/fogbugz_import/importer.rb
+++ b/lib/gitlab/fogbugz_import/importer.rb
@@ -199,7 +199,7 @@ module Gitlab
         s = s.gsub(/^#/, "\\#")
         s = s.gsub(/^-/, "\\-")
         s = s.gsub("`", "\\~")
-        s = s.gsub("\r", "")
+        s = s.delete("\r")
         s = s.gsub("\n", "  \n")
         s
       end
diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb
index 8b1b6f48ed500573377c4e91ed59405f12b28c0a..e0163499e3094066b386167d9527eaa60f6158fa 100644
--- a/lib/gitlab/fogbugz_import/project_creator.rb
+++ b/lib/gitlab/fogbugz_import/project_creator.rb
@@ -12,7 +12,8 @@ module Gitlab
       end
 
       def execute
-        project = ::Projects::CreateService.new(current_user,
+        project = ::Projects::CreateService.new(
+          current_user,
           name: repo.safe_name,
           path: repo.path,
           namespace: namespace,
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 0c350d7c675e1977077944a7c1eff2f5a355c93b..f065cc5e9e9962c70407370403881452dab5c795 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -20,6 +20,10 @@ module Gitlab
       def blank_ref?(ref)
         ref == BLANK_SHA
       end
+
+      def version
+        Gitlab::VersionInfo.parse(Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --version)).first)
+      end
     end
   end
 end
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index dd393fe09d201e8739e3fc5224061565fb36a844..07b856ca64cffec10fa8922eacf8a337bdca17d7 100644
--- a/lib/gitlab/git/hook.rb
+++ b/lib/gitlab/git/hook.rb
@@ -16,6 +16,17 @@ module Gitlab
       def trigger(gl_id, oldrev, newrev, ref)
         return true unless exists?
 
+        case name
+        when "pre-receive", "post-receive"
+          call_receive_hook(gl_id, oldrev, newrev, ref)
+        when "update"
+          call_update_hook(gl_id, oldrev, newrev, ref)
+        end
+      end
+
+      private
+
+      def call_receive_hook(gl_id, oldrev, newrev, ref)
         changes = [oldrev, newrev, ref].join(" ")
 
         # function  will return true if succesful
@@ -54,6 +65,12 @@ module Gitlab
 
         exit_status
       end
+
+      def call_update_hook(gl_id, oldrev, newrev, ref)
+        Dir.chdir(repo_path) do
+          system({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev)
+        end
+      end
     end
   end
 end
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index 270cbcd9ccd92b9ec35da9d90ec688b81f05b983..74d1529e1ff55bc3ce20b6d5ba70f84acc4d2ad6 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -46,7 +46,7 @@ module Gitlab
       end
 
       def github_options
-        OmniAuth::Strategies::GitHub.default_options[:client_options].symbolize_keys
+        OmniAuth::Strategies::GitHub.default_options[:client_options].to_h.symbolize_keys
       end
     end
   end
diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb
index 9c00896c913bf080e0aee65bb8d581d2e315b45b..86fb6c51765208fbb8698fdf9d9db5b68f22cad1 100644
--- a/lib/gitlab/gitlab_import/client.rb
+++ b/lib/gitlab/gitlab_import/client.rb
@@ -75,7 +75,7 @@ module Gitlab
       end
 
       def gitlab_options
-        OmniAuth::Strategies::GitLab.default_options[:client_options].symbolize_keys
+        OmniAuth::Strategies::GitLab.default_options[:client_options].to_h.symbolize_keys
       end
     end
   end
diff --git a/lib/gitlab/gitlab_import/project_creator.rb b/lib/gitlab/gitlab_import/project_creator.rb
index d9452de6a5093b99990a79f44fb29ce1986d6098..7baaadb813c60ab555f2f4c993bc6c5b5f24c2dc 100644
--- a/lib/gitlab/gitlab_import/project_creator.rb
+++ b/lib/gitlab/gitlab_import/project_creator.rb
@@ -11,7 +11,8 @@ module Gitlab
       end
 
       def execute
-        project = ::Projects::CreateService.new(current_user,
+        project = ::Projects::CreateService.new(
+          current_user,
           name: repo["name"],
           path: repo["path"],
           description: repo["description"],
diff --git a/lib/gitlab/gitorious_import/project_creator.rb b/lib/gitlab/gitorious_import/project_creator.rb
index cc9a91c91f4538faa2158d8b11234874863b128e..8e22aa9286ddf4d2a332f2daca87a6ab5f2eee9d 100644
--- a/lib/gitlab/gitorious_import/project_creator.rb
+++ b/lib/gitlab/gitorious_import/project_creator.rb
@@ -10,7 +10,8 @@ module Gitlab
       end
 
       def execute
-        ::Projects::CreateService.new(current_user,
+        ::Projects::CreateService.new(
+          current_user,
           name: repo.name,
           path: repo.path,
           description: repo.description,
diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb
index 87fee28dc010adfcae54c903c427a67d8634b54a..62da327931faff96264b4152325c321bc7f16281 100644
--- a/lib/gitlab/google_code_import/importer.rb
+++ b/lib/gitlab/google_code_import/importer.rb
@@ -171,8 +171,6 @@ module Gitlab
         when /\AMilestone:/
           "#fee3ff"
 
-        when *@closed_statuses.map { |s| nice_status_name(s) }
-          "#cfcfcf"
         when "Status: New"
           "#428bca"
         when "Status: Accepted"
@@ -199,6 +197,8 @@ module Gitlab
           "#8e44ad"
         when "Type: Other"
           "#7f8c8d"
+        when *@closed_statuses.map { |s| nice_status_name(s) }
+          "#cfcfcf"
         else
           "#e2e2e2"
         end
@@ -227,7 +227,7 @@ module Gitlab
         s = s.gsub("`", "\\`")
 
         # Carriage returns make me sad
-        s = s.gsub("\r", "")
+        s = s.delete("\r")
 
         # Markdown ignores single newlines, but we need them as <br />.
         s = s.gsub("\n", "  \n")
diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb
index 1cb7d16aeb3f0021ea7528f29b5be797e3b2caa1..87821c2346094f94869b8144a2a6412c739618af 100644
--- a/lib/gitlab/google_code_import/project_creator.rb
+++ b/lib/gitlab/google_code_import/project_creator.rb
@@ -11,7 +11,8 @@ module Gitlab
       end
 
       def execute
-        project = ::Projects::CreateService.new(current_user,
+        project = ::Projects::CreateService.new(
+          current_user,
           name: repo.name,
           path: repo.name,
           description: repo.summary,
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index 16ff03c38d4f21736d4429bc38dfbdf2a1487612..c438a3d167b8e1d1ac42d7c7403b0a5eb97f7595 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -37,13 +37,15 @@ module Gitlab
 
           # Block user in GitLab if he/she was blocked in AD
           if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
-            user.block unless user.blocked?
+            user.block
             false
           else
             user.activate if user.blocked? && !ldap_config.block_auto_created_users
             true
           end
         else
+          # Block the user if they no longer exist in LDAP/AD
+          user.block 
           false
         end
       rescue
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index 4be99dd88c29dd87ba829973da60aeca464dbf2d..aef08c97d1d7edf18a23b02f82a0eedcb5af40e1 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -14,7 +14,7 @@ module Gitlab
           # LDAP distinguished name is case-insensitive
           identity = ::Identity.
             where(provider: provider).
-            where('lower(extern_uid) = ?', uid.mb_chars.downcase.to_s).last
+            iwhere(extern_uid: uid).last
           identity && identity.user
         end
       end
@@ -31,7 +31,7 @@ module Gitlab
 
       def find_by_uid_and_provider
         self.class.find_by_uid_and_provider(
-          auth_hash.uid.downcase, auth_hash.provider)
+          auth_hash.uid, auth_hash.provider)
       end
 
       def find_by_email
@@ -47,7 +47,7 @@ module Gitlab
         # find_or_initialize_by doesn't update `gl_user.identities`, and isn't autosaved.
         identity = gl_user.identities.find { |identity|  identity.provider == auth_hash.provider }
         identity ||= gl_user.identities.build(provider: auth_hash.provider)
-        
+
         # For a new user set extern_uid to the LDAP DN
         # For an existing user with matching email but changed DN, update the DN.
         # For an existing user with no change in DN, this line changes nothing.
diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb
index c18dfbd485dcbbf03a8cca869f9c28117ba41b07..9d9617761b3b2be1284e30554c4a9904296c87fd 100644
--- a/lib/gitlab/lfs/response.rb
+++ b/lib/gitlab/lfs/response.rb
@@ -220,7 +220,7 @@ module Gitlab
 
       def storage_project(project)
         if project.forked?
-          project.forked_from_project
+          storage_project(project.forked_from_project)
         else
           project
         end
@@ -260,7 +260,7 @@ module Gitlab
       end
 
       def link_to_project(object)
-        if object && !object.projects.exists?(@project)
+        if object && !object.projects.exists?(@project.id)
           object.projects << @project
           object.save
         end
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
deleted file mode 100644
index b082bfc434bca43f89185a53e5e72db89015f1a6..0000000000000000000000000000000000000000
--- a/lib/gitlab/markdown.rb
+++ /dev/null
@@ -1,200 +0,0 @@
-require 'html/pipeline'
-
-module Gitlab
-  # Custom parser for GitLab-flavored Markdown
-  #
-  # See the files in `lib/gitlab/markdown/` for specific processing information.
-  module Markdown
-    # Convert a Markdown String into an HTML-safe String of HTML
-    #
-    # Note that while the returned HTML will have been sanitized of dangerous
-    # HTML, it may post a risk of information leakage if it's not also passed
-    # through `post_process`.
-    #
-    # Also note that the returned String is always HTML, not XHTML. Views
-    # requiring XHTML, such as Atom feeds, need to call `post_process` on the
-    # result, providing the appropriate `pipeline` option.
-    #
-    # markdown - Markdown String
-    # context  - Hash of context options passed to our HTML Pipeline
-    #
-    # Returns an HTML-safe String
-    def self.render(markdown, context = {})
-      html = renderer.render(markdown)
-      html = gfm(html, context)
-
-      html.html_safe
-    end
-
-    # Convert a Markdown String into HTML without going through the HTML
-    # Pipeline.
-    #
-    # Note that because the pipeline is skipped, SanitizationFilter is as well.
-    # Do not output the result of this method to the user.
-    #
-    # markdown - Markdown String
-    #
-    # Returns a String
-    def self.render_without_gfm(markdown)
-      renderer.render(markdown)
-    end
-
-    # Perform post-processing on an HTML String
-    #
-    # This method is used to perform state-dependent changes to a String of
-    # HTML, such as removing references that the current user doesn't have
-    # permission to make (`RedactorFilter`).
-    #
-    # html     - String to process
-    # options  - Hash of options to customize output
-    #            :pipeline  - Symbol pipeline type
-    #            :project   - Project
-    #            :user      - User object
-    #
-    # Returns an HTML-safe String
-    def self.post_process(html, options)
-      context = {
-        project:      options[:project],
-        current_user: options[:user]
-      }
-      doc = post_processor.to_document(html, context)
-
-      if options[:pipeline] == :atom
-        doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML)
-      else
-        doc.to_html
-      end.html_safe
-    end
-
-    # Provide autoload paths for filters to prevent a circular dependency error
-    autoload :AutolinkFilter,               'gitlab/markdown/autolink_filter'
-    autoload :CommitRangeReferenceFilter,   'gitlab/markdown/commit_range_reference_filter'
-    autoload :CommitReferenceFilter,        'gitlab/markdown/commit_reference_filter'
-    autoload :EmojiFilter,                  'gitlab/markdown/emoji_filter'
-    autoload :ExternalIssueReferenceFilter, 'gitlab/markdown/external_issue_reference_filter'
-    autoload :ExternalLinkFilter,           'gitlab/markdown/external_link_filter'
-    autoload :IssueReferenceFilter,         'gitlab/markdown/issue_reference_filter'
-    autoload :LabelReferenceFilter,         'gitlab/markdown/label_reference_filter'
-    autoload :MergeRequestReferenceFilter,  'gitlab/markdown/merge_request_reference_filter'
-    autoload :RedactorFilter,               'gitlab/markdown/redactor_filter'
-    autoload :RelativeLinkFilter,           'gitlab/markdown/relative_link_filter'
-    autoload :SanitizationFilter,           'gitlab/markdown/sanitization_filter'
-    autoload :SnippetReferenceFilter,       'gitlab/markdown/snippet_reference_filter'
-    autoload :SyntaxHighlightFilter,        'gitlab/markdown/syntax_highlight_filter'
-    autoload :TableOfContentsFilter,        'gitlab/markdown/table_of_contents_filter'
-    autoload :TaskListFilter,               'gitlab/markdown/task_list_filter'
-    autoload :UserReferenceFilter,          'gitlab/markdown/user_reference_filter'
-    autoload :UploadLinkFilter,             'gitlab/markdown/upload_link_filter'
-
-    # Public: Parse the provided HTML with GitLab-Flavored Markdown
-    #
-    # html    - HTML String
-    # options - A Hash of options used to customize output (default: {})
-    #           :no_header_anchors - Disable header anchors in TableOfContentsFilter
-    #           :path              - Current path String
-    #           :pipeline          - Symbol pipeline type
-    #           :project           - Current Project object
-    #           :project_wiki      - Current ProjectWiki object
-    #           :ref               - Current ref String
-    #
-    # Returns an HTML-safe String
-    def self.gfm(html, options = {})
-      return '' unless html.present?
-
-      @pipeline ||= HTML::Pipeline.new(filters)
-
-      context = {
-        # SanitizationFilter
-        pipeline: options[:pipeline],
-
-        # EmojiFilter
-        asset_host: Gitlab::Application.config.asset_host,
-        asset_root: Gitlab.config.gitlab.base_url,
-
-        # ReferenceFilter
-        only_path: only_path_pipeline?(options[:pipeline]),
-        project:   options[:project],
-
-        # RelativeLinkFilter
-        project_wiki:   options[:project_wiki],
-        ref:            options[:ref],
-        requested_path: options[:path],
-
-        # TableOfContentsFilter
-        no_header_anchors: options[:no_header_anchors]
-      }
-
-      @pipeline.to_html(html, context).html_safe
-    end
-
-    private
-
-    # Check if a pipeline enables the `only_path` context option
-    #
-    # Returns Boolean
-    def self.only_path_pipeline?(pipeline)
-      case pipeline
-      when :atom, :email
-        false
-      else
-        true
-      end
-    end
-
-    def self.redcarpet_options
-      # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
-      @redcarpet_options ||= {
-        fenced_code_blocks:  true,
-        footnotes:           true,
-        lax_spacing:         true,
-        no_intra_emphasis:   true,
-        space_after_headers: true,
-        strikethrough:       true,
-        superscript:         true,
-        tables:              true
-      }.freeze
-    end
-
-    def self.renderer
-      @markdown ||= begin
-        renderer = Redcarpet::Render::HTML.new
-        Redcarpet::Markdown.new(renderer, redcarpet_options)
-      end
-    end
-
-    def self.post_processor
-      @post_processor ||= HTML::Pipeline.new([Gitlab::Markdown::RedactorFilter])
-    end
-
-    # Filters used in our pipeline
-    #
-    # SanitizationFilter should come first so that all generated reference HTML
-    # goes through untouched.
-    #
-    # See https://github.com/jch/html-pipeline#filters for more filters.
-    def self.filters
-      [
-        Gitlab::Markdown::SyntaxHighlightFilter,
-        Gitlab::Markdown::SanitizationFilter,
-
-        Gitlab::Markdown::UploadLinkFilter,
-        Gitlab::Markdown::RelativeLinkFilter,
-        Gitlab::Markdown::EmojiFilter,
-        Gitlab::Markdown::TableOfContentsFilter,
-        Gitlab::Markdown::AutolinkFilter,
-        Gitlab::Markdown::ExternalLinkFilter,
-
-        Gitlab::Markdown::UserReferenceFilter,
-        Gitlab::Markdown::IssueReferenceFilter,
-        Gitlab::Markdown::ExternalIssueReferenceFilter,
-        Gitlab::Markdown::MergeRequestReferenceFilter,
-        Gitlab::Markdown::SnippetReferenceFilter,
-        Gitlab::Markdown::CommitRangeReferenceFilter,
-        Gitlab::Markdown::CommitReferenceFilter,
-        Gitlab::Markdown::LabelReferenceFilter,
-
-        Gitlab::Markdown::TaskListFilter
-      ]
-    end
-  end
-end
diff --git a/lib/gitlab/markdown/abstract_reference_filter.rb b/lib/gitlab/markdown/abstract_reference_filter.rb
deleted file mode 100644
index fd5b7eb9332e72715286415138f48740b727dbe3..0000000000000000000000000000000000000000
--- a/lib/gitlab/markdown/abstract_reference_filter.rb
+++ /dev/null
@@ -1,100 +0,0 @@
-require 'gitlab/markdown'
-
-module Gitlab
-  module Markdown
-    # Issues, Snippets and Merge Requests shares similar functionality in refernce filtering.
-    # All this functionality moved to this class
-    class AbstractReferenceFilter < ReferenceFilter
-      include CrossProjectReference
-
-      def self.object_class
-        # Implement in child class
-        # Example: MergeRequest
-      end
-
-      def self.object_name
-        object_class.name.underscore
-      end
-
-      def self.object_sym
-        object_name.to_sym
-      end
-
-      def self.data_reference
-        "data-#{object_name.dasherize}"
-      end
-
-      # Public: Find references in text (like `!123` for merge requests)
-      #
-      #   AnyReferenceFilter.references_in(text) do |match, object|
-      #     "<a href=...>PREFIX#{object}</a>"
-      #   end
-      #
-      # PREFIX - symbol that detects reference (like ! for merge requests)
-      # object - reference object (snippet, merget request etc)
-      # text - String text to search.
-      #
-      # Yields the String match, the Integer referenced object ID, and an optional String
-      # of the external project reference.
-      #
-      # Returns a String replaced with the return of the block.
-      def self.references_in(text)
-        text.gsub(object_class.reference_pattern) do |match|
-          yield match, $~[object_sym].to_i, $~[:project]
-        end
-      end
-
-      def self.referenced_by(node)
-        { object_sym => LazyReference.new(object_class, node.attr(data_reference)) }
-      end
-
-      delegate :object_class, :object_sym, :references_in, to: :class
-
-      def find_object(project, id)
-        # Implement in child class
-        # Example: project.merge_requests.find
-      end
-
-      def url_for_object(object, project)
-        # Implement in child class
-        # Example: project_merge_request_url
-      end
-
-      def call
-        replace_text_nodes_matching(object_class.reference_pattern) do |content|
-          object_link_filter(content)
-        end
-      end
-
-      # Replace references (like `!123` for merge requests) in text with links
-      # to the referenced object's details page.
-      #
-      # text - String text to replace references in.
-      #
-      # Returns a String with references replaced with links. All links
-      # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
-      def object_link_filter(text)
-        references_in(text) do |match, id, project_ref|
-          project = project_from_ref(project_ref)
-
-          if project && object = find_object(project, id)
-            title = escape_once("#{object_title}: #{object.title}")
-            klass = reference_class(object_sym)
-            data  = data_attribute(project: project.id, object_sym => object.id)
-            url = url_for_object(object, project)
-
-            %(<a href="#{url}" #{data}
-                 title="#{title}"
-                 class="#{klass}">#{match}</a>)
-          else
-            match
-          end
-        end
-      end
-
-      def object_title
-        object_class.name.titleize
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb
deleted file mode 100644
index e070edae0a4c43f7747c4a207955f051ebbe265f..0000000000000000000000000000000000000000
--- a/lib/gitlab/markdown/commit_range_reference_filter.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-require 'gitlab/markdown'
-
-module Gitlab
-  module Markdown
-    # HTML filter that replaces commit range references with links.
-    #
-    # This filter supports cross-project references.
-    class CommitRangeReferenceFilter < ReferenceFilter
-      include CrossProjectReference
-
-      # Public: Find commit range references in text
-      #
-      #   CommitRangeReferenceFilter.references_in(text) do |match, commit_range, project_ref|
-      #     "<a href=...>#{commit_range}</a>"
-      #   end
-      #
-      # text - String text to search.
-      #
-      # Yields the String match, the String commit range, and an optional String
-      # of the external project reference.
-      #
-      # Returns a String replaced with the return of the block.
-      def self.references_in(text)
-        text.gsub(CommitRange.reference_pattern) do |match|
-          yield match, $~[:commit_range], $~[:project]
-        end
-      end
-
-      def self.referenced_by(node)
-        project = Project.find(node.attr("data-project")) rescue nil
-        return unless project
-
-        id = node.attr("data-commit-range")
-        range = CommitRange.new(id, project)
-
-        return unless range.valid_commits?
-
-        { commit_range: range }
-      end
-
-      def initialize(*args)
-        super
-
-        @commit_map = {}
-      end
-
-      def call
-        replace_text_nodes_matching(CommitRange.reference_pattern) do |content|
-          commit_range_link_filter(content)
-        end
-      end
-
-      # Replace commit range references in text with links to compare the commit
-      # ranges.
-      #
-      # text - String text to replace references in.
-      #
-      # Returns a String with commit range references replaced with links. All
-      # links have `gfm` and `gfm-commit_range` class names attached for
-      # styling.
-      def commit_range_link_filter(text)
-        self.class.references_in(text) do |match, id, project_ref|
-          project = self.project_from_ref(project_ref)
-
-          range = CommitRange.new(id, project)
-
-          if range.valid_commits?
-            url = url_for_commit_range(project, range)
-
-            title = range.reference_title
-            klass = reference_class(:commit_range)
-            data  = data_attribute(project: project.id, commit_range: id)
-
-            project_ref += '@' if project_ref
-
-            %(<a href="#{url}" #{data}
-                 title="#{title}"
-                 class="#{klass}">#{project_ref}#{range}</a>)
-          else
-            match
-          end
-        end
-      end
-
-      def url_for_commit_range(project, range)
-        h = Gitlab::Application.routes.url_helpers
-        h.namespace_project_compare_url(project.namespace, project,
-                                        range.to_param.merge(only_path: context[:only_path]))
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb
deleted file mode 100644
index 8cdbeb1f9cf7101b0201a65003c125e9f8e5ffcf..0000000000000000000000000000000000000000
--- a/lib/gitlab/markdown/commit_reference_filter.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-require 'gitlab/markdown'
-
-module Gitlab
-  module Markdown
-    # HTML filter that replaces commit references with links.
-    #
-    # This filter supports cross-project references.
-    class CommitReferenceFilter < ReferenceFilter
-      include CrossProjectReference
-
-      # Public: Find commit references in text
-      #
-      #   CommitReferenceFilter.references_in(text) do |match, commit, project_ref|
-      #     "<a href=...>#{commit}</a>"
-      #   end
-      #
-      # text - String text to search.
-      #
-      # Yields the String match, the String commit identifier, and an optional
-      # String of the external project reference.
-      #
-      # Returns a String replaced with the return of the block.
-      def self.references_in(text)
-        text.gsub(Commit.reference_pattern) do |match|
-          yield match, $~[:commit], $~[:project]
-        end
-      end
-
-      def self.referenced_by(node)
-        project = Project.find(node.attr("data-project")) rescue nil
-        return unless project
-
-        id = node.attr("data-commit")
-        commit = commit_from_ref(project, id)
-
-        return unless commit
-
-        { commit: commit }
-      end
-
-      def call
-        replace_text_nodes_matching(Commit.reference_pattern) do |content|
-          commit_link_filter(content)
-        end
-      end
-
-      # Replace commit references in text with links to the commit specified.
-      #
-      # text - String text to replace references in.
-      #
-      # Returns a String with commit references replaced with links. All links
-      # have `gfm` and `gfm-commit` class names attached for styling.
-      def commit_link_filter(text)
-        self.class.references_in(text) do |match, id, project_ref|
-          project = self.project_from_ref(project_ref)
-
-          if commit = self.class.commit_from_ref(project, id)
-            url = url_for_commit(project, commit)
-
-            title = escape_once(commit.link_title)
-            klass = reference_class(:commit)
-            data  = data_attribute(project: project.id, commit: id)
-
-            project_ref += '@' if project_ref
-
-            %(<a href="#{url}" #{data}
-                 title="#{title}"
-                 class="#{klass}">#{project_ref}#{commit.short_id}</a>)
-          else
-            match
-          end
-        end
-      end
-
-      def self.commit_from_ref(project, id)
-        if project && project.valid_repo?
-          project.commit(id)
-        end
-      end
-
-      def url_for_commit(project, commit)
-        h = Gitlab::Application.routes.url_helpers
-        h.namespace_project_commit_url(project.namespace, project, commit,
-                                        only_path: context[:only_path])
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/markdown/cross_project_reference.rb b/lib/gitlab/markdown/cross_project_reference.rb
deleted file mode 100644
index 6ab04a584b0c8e8166a9e5cc908b5ac7e6140897..0000000000000000000000000000000000000000
--- a/lib/gitlab/markdown/cross_project_reference.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'gitlab/markdown'
-
-module Gitlab
-  module Markdown
-    # Common methods for ReferenceFilters that support an optional cross-project
-    # reference.
-    module CrossProjectReference
-      # Given a cross-project reference string, get the Project record
-      #
-      # Defaults to value of `context[:project]` if:
-      # * No reference is given OR
-      # * Reference given doesn't exist
-      #
-      # ref - String reference.
-      #
-      # Returns a Project, or nil if the reference can't be found
-      def project_from_ref(ref)
-        return context[:project] unless ref
-
-        Project.find_with_namespace(ref)
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/markdown/pipeline.rb b/lib/gitlab/markdown/pipeline.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8f3f43c0e914d72151d91189783b6a49810834cb
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline.rb
@@ -0,0 +1,34 @@
+require 'banzai'
+
+module Gitlab
+  module Markdown
+    class Pipeline
+      def self.[](name)
+        name ||= :full
+        const_get("#{name.to_s.camelize}Pipeline")
+      end
+
+      def self.filters
+        []
+      end
+
+      def self.transform_context(context)
+        context
+      end
+
+      def self.html_pipeline
+        @html_pipeline ||= HTML::Pipeline.new(filters)
+      end
+
+      class << self
+        %i(call to_document to_html).each do |meth|
+          define_method(meth) do |text, context|
+            context = transform_context(context)
+
+            html_pipeline.send(meth, text, context)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2d266ccfe9e64252996ff290ed1d42ab4c6a09c2
--- /dev/null
+++ b/lib/gitlab/metrics.rb
@@ -0,0 +1,104 @@
+module Gitlab
+  module Metrics
+    extend Gitlab::CurrentSettings
+
+    RAILS_ROOT   = Rails.root.to_s
+    METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s
+    PATH_REGEX   = /^#{RAILS_ROOT}\/?/
+
+    def self.pool_size
+      current_application_settings[:metrics_pool_size] || 16
+    end
+
+    def self.timeout
+      current_application_settings[:metrics_timeout] || 10
+    end
+
+    def self.enabled?
+      current_application_settings[:metrics_enabled] || false
+    end
+
+    def self.mri?
+      RUBY_ENGINE == 'ruby'
+    end
+
+    def self.method_call_threshold
+      # This is memoized since this method is called for every instrumented
+      # method. Loading data from an external cache on every method call slows
+      # things down too much.
+      @method_call_threshold ||=
+        (current_application_settings[:metrics_method_call_threshold] || 10)
+    end
+
+    def self.pool
+      @pool
+    end
+
+    def self.hostname
+      @hostname
+    end
+
+    # Returns a relative path and line number based on the last application call
+    # frame.
+    def self.last_relative_application_frame
+      frame = caller_locations.find do |l|
+        l.path.start_with?(RAILS_ROOT) && !l.path.start_with?(METRICS_ROOT)
+      end
+
+      if frame
+        return frame.path.sub(PATH_REGEX, ''), frame.lineno
+      else
+        return nil, nil
+      end
+    end
+
+    def self.submit_metrics(metrics)
+      prepared = prepare_metrics(metrics)
+
+      pool.with do |connection|
+        prepared.each do |metric|
+          begin
+            connection.write_points([metric])
+          rescue StandardError
+          end
+        end
+      end
+    end
+
+    def self.prepare_metrics(metrics)
+      metrics.map do |hash|
+        new_hash = hash.symbolize_keys
+
+        new_hash[:tags].each do |key, value|
+          if value.blank?
+            new_hash[:tags].delete(key)
+          else
+            new_hash[:tags][key] = escape_value(value)
+          end
+        end
+
+        new_hash
+      end
+    end
+
+    def self.escape_value(value)
+      value.to_s.gsub('=', '\\=')
+    end
+
+    @hostname = Socket.gethostname
+
+    # When enabled this should be set before being used as the usual pattern
+    # "@foo ||= bar" is _not_ thread-safe.
+    if enabled?
+      @pool = ConnectionPool.new(size: pool_size, timeout: timeout) do
+        host = current_application_settings[:metrics_host]
+        user = current_application_settings[:metrics_username]
+        pw   = current_application_settings[:metrics_password]
+        port = current_application_settings[:metrics_port]
+
+        InfluxDB::Client.
+          new(udp: { host: host, port: port }, username: user, password: pw)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/delta.rb b/lib/gitlab/metrics/delta.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bcf28eed84d897c7f08cfd6691cbc9c4c8fadb68
--- /dev/null
+++ b/lib/gitlab/metrics/delta.rb
@@ -0,0 +1,32 @@
+module Gitlab
+  module Metrics
+    # Class for calculating the difference between two numeric values.
+    #
+    # Every call to `compared_with` updates the internal value. This makes it
+    # possible to use a single Delta instance to calculate the delta over time
+    # of an ever increasing number.
+    #
+    # Example usage:
+    #
+    #     delta = Delta.new(0)
+    #
+    #     delta.compared_with(10) # => 10
+    #     delta.compared_with(15) # => 5
+    #     delta.compared_with(20) # => 5
+    class Delta
+      def initialize(value = 0)
+        @value = value
+      end
+
+      # new_value - The value to compare with as a Numeric.
+      #
+      # Returns a new Numeric (depending on the type of `new_value`).
+      def compared_with(new_value)
+        delta  = new_value - @value
+        @value = new_value
+
+        delta
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
new file mode 100644
index 0000000000000000000000000000000000000000..06fc2f259483d713e8a6340496980f27b5dfbf23
--- /dev/null
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -0,0 +1,146 @@
+module Gitlab
+  module Metrics
+    # Module for instrumenting methods.
+    #
+    # This module allows instrumenting of methods without having to actually
+    # alter the target code (e.g. by including modules).
+    #
+    # Example usage:
+    #
+    #     Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login)
+    module Instrumentation
+      SERIES = 'method_calls'
+
+      def self.configure
+        yield self
+      end
+
+      # Instruments a class method.
+      #
+      # mod  - The module to instrument as a Module/Class.
+      # name - The name of the method to instrument.
+      def self.instrument_method(mod, name)
+        instrument(:class, mod, name)
+      end
+
+      # Instruments an instance method.
+      #
+      # mod  - The module to instrument as a Module/Class.
+      # name - The name of the method to instrument.
+      def self.instrument_instance_method(mod, name)
+        instrument(:instance, mod, name)
+      end
+
+      # Recursively instruments all subclasses of the given root module.
+      #
+      # This can be used to for example instrument all ActiveRecord models (as
+      # these all inherit from ActiveRecord::Base).
+      #
+      # This method can optionally take a block to pass to `instrument_methods`
+      # and `instrument_instance_methods`.
+      #
+      # root - The root module for which to instrument subclasses. The root
+      #        module itself is not instrumented.
+      def self.instrument_class_hierarchy(root, &block)
+        visit = root.subclasses
+
+        until visit.empty?
+          klass = visit.pop
+
+          instrument_methods(klass, &block)
+          instrument_instance_methods(klass, &block)
+
+          klass.subclasses.each { |c| visit << c }
+        end
+      end
+
+      # Instruments all public methods of a module.
+      #
+      # This method optionally takes a block that can be used to determine if a
+      # method should be instrumented or not. The block is passed the receiving
+      # module and an UnboundMethod. If the block returns a non truthy value the
+      # method is not instrumented.
+      #
+      # mod - The module to instrument.
+      def self.instrument_methods(mod)
+        mod.public_methods(false).each do |name|
+          method = mod.method(name)
+
+          if method.owner == mod.singleton_class
+            if !block_given? || block_given? && yield(mod, method)
+              instrument_method(mod, name)
+            end
+          end
+        end
+      end
+
+      # Instruments all public instance methods of a module.
+      #
+      # See `instrument_methods` for more information.
+      #
+      # mod - The module to instrument.
+      def self.instrument_instance_methods(mod)
+        mod.public_instance_methods(false).each do |name|
+          method = mod.instance_method(name)
+
+          if method.owner == mod
+            if !block_given? || block_given? && yield(mod, method)
+              instrument_instance_method(mod, name)
+            end
+          end
+        end
+      end
+
+      # Instruments a method.
+      #
+      # type - The type (:class or :instance) of method to instrument.
+      # mod  - The module containing the method.
+      # name - The name of the method to instrument.
+      def self.instrument(type, mod, name)
+        return unless Metrics.enabled?
+
+        name       = name.to_sym
+        alias_name = :"_original_#{name}"
+        target     = type == :instance ? mod : mod.singleton_class
+
+        if type == :instance
+          target = mod
+          label  = "#{mod.name}##{name}"
+        else
+          target = mod.singleton_class
+          label  = "#{mod.name}.#{name}"
+        end
+
+        target.class_eval <<-EOF, __FILE__, __LINE__ + 1
+          alias_method #{alias_name.inspect}, #{name.inspect}
+
+          def #{name}(*args, &block)
+            trans = Gitlab::Metrics::Instrumentation.transaction
+
+            if trans
+              start    = Time.now
+              retval   = __send__(#{alias_name.inspect}, *args, &block)
+              duration = (Time.now - start) * 1000.0
+
+              if duration >= Gitlab::Metrics.method_call_threshold
+                trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES,
+                                 { duration: duration },
+                                 method: #{label.inspect})
+              end
+
+              retval
+            else
+              __send__(#{alias_name.inspect}, *args, &block)
+            end
+          end
+        EOF
+      end
+
+      # Small layer of indirection to make it easier to stub out the current
+      # transaction.
+      def self.transaction
+        Transaction.current
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb
new file mode 100644
index 0000000000000000000000000000000000000000..79241f56874e365fd49dbf04c5e96d1ec45dfb47
--- /dev/null
+++ b/lib/gitlab/metrics/metric.rb
@@ -0,0 +1,34 @@
+module Gitlab
+  module Metrics
+    # Class for storing details of a single metric (label, value, etc).
+    class Metric
+      attr_reader :series, :values, :tags, :created_at
+
+      # series - The name of the series (as a String) to store the metric in.
+      # values - A Hash containing the values to store.
+      # tags   - A Hash containing extra tags to add to the metrics.
+      def initialize(series, values, tags = {})
+        @values     = values
+        @series     = series
+        @tags       = tags
+        @created_at = Time.now.utc
+      end
+
+      # Returns a Hash in a format that can be directly written to InfluxDB.
+      def to_hash
+        {
+          series: @series,
+          tags:   @tags.merge(
+            hostname:       Metrics.hostname,
+            ruby_engine:    RUBY_ENGINE,
+            ruby_version:   RUBY_VERSION,
+            gitlab_version: Gitlab::VERSION,
+            process_type:   Sidekiq.server? ? 'sidekiq' : 'rails'
+          ),
+          values:    @values,
+          timestamp: @created_at.to_i * 1_000_000_000
+        }
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/obfuscated_sql.rb b/lib/gitlab/metrics/obfuscated_sql.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fe97d7a0534e1e160d74051ec4ee30fe7631a3fc
--- /dev/null
+++ b/lib/gitlab/metrics/obfuscated_sql.rb
@@ -0,0 +1,47 @@
+module Gitlab
+  module Metrics
+    # Class for producing SQL queries with sensitive data stripped out.
+    class ObfuscatedSQL
+      REPLACEMENT = /
+        \d+(\.\d+)?      # integers, floats
+        | '.+?'          # single quoted strings
+        | \/.+?(?<!\\)\/ # regexps (including escaped slashes)
+      /x
+
+      MYSQL_REPLACEMENTS = /
+        ".+?" # double quoted strings
+      /x
+
+      # Regex to replace consecutive placeholders with a single one indicating
+      # the length. This can be useful when a "IN" statement uses thousands of
+      # IDs (storing this would just be a waste of space).
+      CONSECUTIVE = /(\?(\s*,\s*)?){2,}/
+
+      # sql - The raw SQL query as a String.
+      def initialize(sql)
+        @sql = sql
+      end
+
+      # Returns a new, obfuscated SQL query.
+      def to_s
+        regex = REPLACEMENT
+
+        if Gitlab::Database.mysql?
+          regex = Regexp.union(regex, MYSQL_REPLACEMENTS)
+        end
+
+        sql = @sql.gsub(regex, '?').gsub(CONSECUTIVE) do |match|
+          "#{match.count(',') + 1} values"
+        end
+
+        # InfluxDB escapes double quotes upon output, so lets get rid of them
+        # whenever we can.
+        if Gitlab::Database.postgresql?
+          sql = sql.delete('"')
+        end
+
+        sql.tr("\n", ' ')
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/rack_middleware.rb b/lib/gitlab/metrics/rack_middleware.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5c0587c4c51396d331a67681650b60b50dfa5df4
--- /dev/null
+++ b/lib/gitlab/metrics/rack_middleware.rb
@@ -0,0 +1,49 @@
+module Gitlab
+  module Metrics
+    # Rack middleware for tracking Rails requests.
+    class RackMiddleware
+      CONTROLLER_KEY = 'action_controller.instance'
+
+      def initialize(app)
+        @app = app
+      end
+
+      # env - A Hash containing Rack environment details.
+      def call(env)
+        trans  = transaction_from_env(env)
+        retval = nil
+
+        begin
+          retval = trans.run { @app.call(env) }
+
+        # Even in the event of an error we want to submit any metrics we
+        # might've gathered up to this point.
+        ensure
+          if env[CONTROLLER_KEY]
+            tag_controller(trans, env)
+          end
+
+          trans.finish
+        end
+
+        retval
+      end
+
+      def transaction_from_env(env)
+        trans = Transaction.new
+
+        trans.add_tag(:request_method, env['REQUEST_METHOD'])
+        trans.add_tag(:request_uri, env['REQUEST_URI'])
+
+        trans
+      end
+
+      def tag_controller(trans, env)
+        controller = env[CONTROLLER_KEY]
+        label      = "#{controller.class.name}##{controller.action_name}"
+
+        trans.add_tag(:action, label)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/sampler.rb
new file mode 100644
index 0000000000000000000000000000000000000000..998578e1c0a704e9f4c120474dfe2baa2e95604b
--- /dev/null
+++ b/lib/gitlab/metrics/sampler.rb
@@ -0,0 +1,98 @@
+module Gitlab
+  module Metrics
+    # Class that sends certain metrics to InfluxDB at a specific interval.
+    #
+    # This class is used to gather statistics that can't be directly associated
+    # with a transaction such as system memory usage, garbage collection
+    # statistics, etc.
+    class Sampler
+      # interval - The sampling interval in seconds.
+      def initialize(interval = 15)
+        @interval = interval
+        @metrics  = []
+
+        @last_minor_gc = Delta.new(GC.stat[:minor_gc_count])
+        @last_major_gc = Delta.new(GC.stat[:major_gc_count])
+
+        if Gitlab::Metrics.mri?
+          require 'allocations'
+
+          Allocations.start
+        end
+      end
+
+      def start
+        Thread.new do
+          Thread.current.abort_on_exception = true
+
+          loop do
+            sleep(@interval)
+
+            sample
+          end
+        end
+      end
+
+      def sample
+        sample_memory_usage
+        sample_file_descriptors
+        sample_objects
+        sample_gc
+
+        flush
+      ensure
+        GC::Profiler.clear
+        @metrics.clear
+      end
+
+      def flush
+        Metrics.submit_metrics(@metrics.map(&:to_hash))
+      end
+
+      def sample_memory_usage
+        @metrics << Metric.new('memory_usage', value: System.memory_usage)
+      end
+
+      def sample_file_descriptors
+        @metrics << Metric.
+          new('file_descriptors', value: System.file_descriptor_count)
+      end
+
+      if Metrics.mri?
+        def sample_objects
+          sample = Allocations.to_hash
+          counts = sample.each_with_object({}) do |(klass, count), hash|
+            hash[klass.name] = count
+          end
+
+          # Symbols aren't allocated so we'll need to add those manually.
+          counts['Symbol'] = Symbol.all_symbols.length
+
+          counts.each do |name, count|
+            @metrics << Metric.new('object_counts', { count: count }, type: name)
+          end
+        end
+      else
+        def sample_objects
+        end
+      end
+
+      def sample_gc
+        time  = GC::Profiler.total_time * 1000.0
+        stats = GC.stat.merge(total_time: time)
+
+        # We want the difference of GC runs compared to the last sample, not the
+        # total amount since the process started.
+        stats[:minor_gc_count] =
+          @last_minor_gc.compared_with(stats[:minor_gc_count])
+
+        stats[:major_gc_count] =
+          @last_major_gc.compared_with(stats[:major_gc_count])
+
+        stats[:count] = stats[:minor_gc_count] + stats[:major_gc_count]
+
+        @metrics << Metric.new('gc_statistics', stats)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/sidekiq_middleware.rb b/lib/gitlab/metrics/sidekiq_middleware.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ad441decfa251670545e86ba10cb0e074c1fc9d2
--- /dev/null
+++ b/lib/gitlab/metrics/sidekiq_middleware.rb
@@ -0,0 +1,23 @@
+module Gitlab
+  module Metrics
+    # Sidekiq middleware for tracking jobs.
+    #
+    # This middleware is intended to be used as a server-side middleware.
+    class SidekiqMiddleware
+      def call(worker, message, queue)
+        trans = Transaction.new
+
+        begin
+          trans.run { yield }
+        ensure
+          tag_worker(trans, worker)
+          trans.finish
+        end
+      end
+
+      def tag_worker(trans, worker)
+        trans.add_tag(:action, "#{worker.class.name}#perform")
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7e0dcf99d926178ccfd52ee176f31a204646edbf
--- /dev/null
+++ b/lib/gitlab/metrics/subscribers/action_view.rb
@@ -0,0 +1,53 @@
+module Gitlab
+  module Metrics
+    module Subscribers
+      # Class for tracking the rendering timings of views.
+      class ActionView < ActiveSupport::Subscriber
+        attach_to :action_view
+
+        SERIES = 'views'
+
+        def render_template(event)
+          track(event) if current_transaction
+        end
+
+        alias_method :render_view, :render_template
+
+        private
+
+        def track(event)
+          values = values_for(event)
+          tags   = tags_for(event)
+
+          current_transaction.add_metric(SERIES, values, tags)
+        end
+
+        def relative_path(path)
+          path.gsub(/^#{Rails.root.to_s}\/?/, '')
+        end
+
+        def values_for(event)
+          { duration: event.duration }
+        end
+
+        def tags_for(event)
+          path = relative_path(event.payload[:identifier])
+          tags = { view: path }
+
+          file, line = Metrics.last_relative_application_frame
+
+          if file and line
+            tags[:file] = file
+            tags[:line] = line
+          end
+
+          tags
+        end
+
+        def current_transaction
+          Transaction.current
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d947c128ce22249a882d0816878bcb7b8bca23b5
--- /dev/null
+++ b/lib/gitlab/metrics/subscribers/active_record.rb
@@ -0,0 +1,48 @@
+module Gitlab
+  module Metrics
+    module Subscribers
+      # Class for tracking raw SQL queries.
+      #
+      # Queries are obfuscated before being logged to ensure no private data is
+      # exposed via InfluxDB/Grafana.
+      class ActiveRecord < ActiveSupport::Subscriber
+        attach_to :active_record
+
+        SERIES = 'sql_queries'
+
+        def sql(event)
+          return unless current_transaction
+
+          values = values_for(event)
+          tags   = tags_for(event)
+
+          current_transaction.add_metric(SERIES, values, tags)
+        end
+
+        private
+
+        def values_for(event)
+          { duration: event.duration }
+        end
+
+        def tags_for(event)
+          sql  = ObfuscatedSQL.new(event.payload[:sql]).to_s
+          tags = { sql: sql }
+
+          file, line = Metrics.last_relative_application_frame
+
+          if file and line
+            tags[:file] = file
+            tags[:line] = line
+          end
+
+          tags
+        end
+
+        def current_transaction
+          Transaction.current
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
new file mode 100644
index 0000000000000000000000000000000000000000..83371265278f6da3b93d1bb34bd0186771e6cc7f
--- /dev/null
+++ b/lib/gitlab/metrics/system.rb
@@ -0,0 +1,35 @@
+module Gitlab
+  module Metrics
+    # Module for gathering system/process statistics such as the memory usage.
+    #
+    # This module relies on the /proc filesystem being available. If /proc is
+    # not available the methods of this module will be stubbed.
+    module System
+      if File.exist?('/proc')
+        # Returns the current process' memory usage in bytes.
+        def self.memory_usage
+          mem   = 0
+          match = File.read('/proc/self/status').match(/VmRSS:\s+(\d+)/)
+
+          if match and match[1]
+            mem = match[1].to_f * 1024
+          end
+
+          mem
+        end
+
+        def self.file_descriptor_count
+          Dir.glob('/proc/self/fd/*').length
+        end
+      else
+        def self.memory_usage
+          0.0
+        end
+
+        def self.file_descriptor_count
+          0
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a61dbd989e782a064cc7f3c0cd7f8074de873579
--- /dev/null
+++ b/lib/gitlab/metrics/transaction.rb
@@ -0,0 +1,66 @@
+module Gitlab
+  module Metrics
+    # Class for storing metrics information of a single transaction.
+    class Transaction
+      THREAD_KEY = :_gitlab_metrics_transaction
+
+      SERIES = 'transactions'
+
+      attr_reader :uuid, :tags
+
+      def self.current
+        Thread.current[THREAD_KEY]
+      end
+
+      # name - The name of this transaction as a String.
+      def initialize
+        @metrics = []
+        @uuid    = SecureRandom.uuid
+
+        @started_at  = nil
+        @finished_at = nil
+
+        @tags = {}
+      end
+
+      def duration
+        @finished_at ? (@finished_at - @started_at) * 1000.0 : 0.0
+      end
+
+      def run
+        Thread.current[THREAD_KEY] = self
+
+        @started_at = Time.now
+
+        yield
+      ensure
+        @finished_at = Time.now
+
+        Thread.current[THREAD_KEY] = nil
+      end
+
+      def add_metric(series, values, tags = {})
+        tags = tags.merge(transaction_id: @uuid)
+
+        @metrics << Metric.new(series, values, tags)
+      end
+
+      def add_tag(key, value)
+        @tags[key] = value
+      end
+
+      def finish
+        track_self
+        submit
+      end
+
+      def track_self
+        add_metric(SERIES, { duration: duration }, @tags)
+      end
+
+      def submit
+        Metrics.submit_metrics(@metrics.map(&:to_hash))
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/o_auth/auth_hash.rb b/lib/gitlab/o_auth/auth_hash.rb
index d94b104bbf86804f2b92e8b1614d0ae95a581a4e..ba31599432bf2d66a4bba0e115fc7dbfb8187d64 100644
--- a/lib/gitlab/o_auth/auth_hash.rb
+++ b/lib/gitlab/o_auth/auth_hash.rb
@@ -62,7 +62,7 @@ module Gitlab
       # Get the first part of the email address (before @)
       # In addtion in removes illegal characters
       def generate_username(email)
-        email.match(/^[^@]*/)[0].parameterize
+        email.match(/^[^@]*/)[0].mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/,'').to_s
       end
 
       def generate_temporarily_email(username)
diff --git a/lib/gitlab/o_auth/session.rb b/lib/gitlab/o_auth/session.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f33bfd0bd0e684e6ca8ba31796b1878889b2c8f1
--- /dev/null
+++ b/lib/gitlab/o_auth/session.rb
@@ -0,0 +1,17 @@
+module Gitlab
+  module OAuth
+    module Session
+      def self.create(provider, ticket)
+        Rails.cache.write("gitlab:#{provider}:#{ticket}", ticket, expires_in: Gitlab.config.omniauth.cas3.session_duration)
+      end
+
+      def self.destroy(provider, ticket)
+        Rails.cache.delete("gitlab:#{provider}:#{ticket}")
+      end
+
+      def self.valid?(provider, ticket)
+        Rails.cache.read("gitlab:#{provider}:#{ticket}").present?
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index 17ce4d4b1747e7004ed7e628b5c01f85008df745..f1a362f5303a296099c4ed7c725312b9498605a1 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -64,7 +64,7 @@ module Gitlab
 
         # If a corresponding person exists with same uid in a LDAP server,
         # set up a Gitlab user with dual LDAP and Omniauth identities.
-        if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn.downcase, ldap_person.provider)
+        if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn, ldap_person.provider)
           # Case when a LDAP user already exists in Gitlab. Add the Omniauth identity to existing account.
           user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider)
         else
diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
index fa068d507634a1351985bab5e2877dfd8484e1e3..4f9cdef3869e5b53743467a97e83c0ef07174bb2 100644
--- a/lib/gitlab/push_data_builder.rb
+++ b/lib/gitlab/push_data_builder.rb
@@ -18,10 +18,7 @@ module Gitlab
       #     homepage: String,
       #   },
       #   commits: Array,
-      #   total_commits_count: Fixnum,
-      #   added: ["CHANGELOG"],
-      #   modified: [],
-      #   removed: ["tmp/file.txt"]
+      #   total_commits_count: Fixnum
       # }
       #
       def build(project, user, oldrev, newrev, ref, commits = [], message = nil)
@@ -33,11 +30,12 @@ module Gitlab
 
         # For performance purposes maximum 20 latest commits
         # will be passed as post receive hook data.
-        commit_attrs = commits_limited.map(&:hook_attrs)
+        commit_attrs = commits_limited.map do |commit|
+          commit.hook_attrs(with_changed_files: true)
+        end
 
         type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push"
 
-        repo_changes = repo_changes(project, newrev, oldrev)
         # Hash to be passed as post_receive_data
         data = {
           object_kind: type,
@@ -60,10 +58,7 @@ module Gitlab
             visibility_level: project.visibility_level
           },
           commits: commit_attrs,
-          total_commits_count: commits_count,
-          added: repo_changes[:added],
-          modified: repo_changes[:modified],
-          removed: repo_changes[:removed]
+          total_commits_count: commits_count
         }
 
         data
@@ -94,27 +89,6 @@ module Gitlab
           newrev
         end
       end
-
-      def repo_changes(project, newrev, oldrev)
-        changes = { added: [], modified: [], removed: [] }
-        compare_result = CompareService.new.
-          execute(project, newrev, project, oldrev)
-
-        if compare_result
-          compare_result.diffs.each do |diff|
-            case true
-            when diff.deleted_file
-              changes[:removed] << diff.old_path
-            when diff.renamed_file, diff.new_file
-              changes[:added] << diff.new_path
-            else
-              changes[:modified] << diff.new_path
-            end
-          end
-        end
-
-        changes
-      end
     end
   end
 end
diff --git a/lib/gitlab/recaptcha.rb b/lib/gitlab/recaptcha.rb
new file mode 100644
index 0000000000000000000000000000000000000000..70e7f25d518f6f62d0b2c969d2c5e104829d707a
--- /dev/null
+++ b/lib/gitlab/recaptcha.rb
@@ -0,0 +1,14 @@
+module Gitlab
+  module Recaptcha
+    def self.load_configurations!
+      if current_application_settings.recaptcha_enabled
+        ::Recaptcha.configure do |config|
+          config.public_key  = current_application_settings.recaptcha_site_key
+          config.private_key = current_application_settings.recaptcha_private_key
+        end
+
+        true
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index da8df8a3025550e5a23d73c19c303fd0894464ef..be795649e59ab6015f114cb196e4c95ad902a58f 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -1,73 +1,42 @@
-require 'gitlab/markdown'
+require 'banzai'
 
 module Gitlab
   # Extract possible GFM references from an arbitrary String for further processing.
-  class ReferenceExtractor
-    attr_accessor :project, :current_user, :load_lazy_references
+  class ReferenceExtractor < Banzai::ReferenceExtractor
+    attr_accessor :project, :current_user, :author
 
-    def initialize(project, current_user = nil, load_lazy_references: true)
+    def initialize(project, current_user = nil, author = nil)
       @project = project
       @current_user = current_user
-      @load_lazy_references = load_lazy_references
+      @author = author
+
+      @references = {}
+
+      super()
     end
 
-    def analyze(text)
-      references.clear
-      @text = Gitlab::Markdown.render_without_gfm(text)
+    def analyze(text, context = {})
+      super(text, context.merge(project: project))
     end
 
-    %i(user label issue merge_request snippet commit commit_range).each do |type|
+    %i(user label merge_request snippet commit commit_range).each do |type|
       define_method("#{type}s") do
-        references[type]
+        @references[type] ||= references(type, reference_context)
       end
     end
 
-    private
-
-    def references
-      @references ||= Hash.new do |references, type|
-        type = type.to_sym
-        next references[type] if references.has_key?(type)
-
-        references[type] = pipeline_result(type)
+    def issues
+      if project && project.jira_tracker?
+        @references[:external_issue] ||= references(:external_issue, reference_context)
+      else
+        @references[:issue] ||= references(:issue, reference_context)
       end
     end
 
-    # Instantiate and call HTML::Pipeline with a single reference filter type,
-    # returning the result
-    #
-    # filter_type - Symbol reference type (e.g., :commit, :issue, etc.)
-    #
-    # Returns the results Array for the requested filter type
-    def pipeline_result(filter_type)
-      return [] if @text.blank?
-      
-      klass  = "#{filter_type.to_s.camelize}ReferenceFilter"
-      filter = Gitlab::Markdown.const_get(klass)
-
-      context = {
-        project: project,
-        current_user: current_user,
-        
-        # We don't actually care about the links generated
-        only_path: true,
-        ignore_blockquotes: true,
-
-        # ReferenceGathererFilter
-        load_lazy_references: false,
-        reference_filter:     filter
-      }
-
-      pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context)
-      result = pipeline.call(@text)
-
-      values = result[:references][filter_type].uniq
-
-      if @load_lazy_references
-        values = Gitlab::Markdown::ReferenceFilter::LazyReference.load(values).uniq
-      end
+    private
 
-      values
+    def reference_context
+      { project: project, current_user: current_user, author: author }
     end
   end
 end
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index 31aa3528c4c37a4aa4416da61e6d0df01368b9c0..2ef0e982256cdefa8f60090221d4534053e2ed6e 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -14,7 +14,7 @@ module Gitlab
 
     def self.mute_mailer
       code = <<-eos
-def Notify.delay
+def Notify.deliver_later
   self
 end
       eos
diff --git a/lib/gitlab/sherlock/transaction.rb b/lib/gitlab/sherlock/transaction.rb
index d87a4c9bb4a82a5e59dc6dbe87dfe0c6436e6673..3489fb251b6be312cd6d63ed5103e289776cad3e 100644
--- a/lib/gitlab/sherlock/transaction.rb
+++ b/lib/gitlab/sherlock/transaction.rb
@@ -36,6 +36,11 @@ module Gitlab
         @duration ||= started_at && finished_at ? finished_at - started_at : 0
       end
 
+      # Returns the total query duration in seconds.
+      def query_duration
+        @query_duration ||= @queries.map { |q| q.duration }.inject(:+) / 1000.0
+      end
+
       def to_param
         @id
       end
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 335dc44be19d2cee966cbb6d407d11c0f4db23d4..3160a3c7582547aa117bae1285e0e3ce9424e2d4 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -51,6 +51,15 @@ module Gitlab
       def allowed_fork_levels(origin_level)
         [PRIVATE, INTERNAL, PUBLIC].select{ |level| level <= origin_level }
       end
+
+      def level_name(level)
+        level_name = 'Unknown'
+        options.each do |name, lvl|
+          level_name = name if lvl == level.to_i
+        end
+
+        level_name
+      end
     end
 
     def private?
diff --git a/lib/omni_auth/request_forgery_protection.rb b/lib/omni_auth/request_forgery_protection.rb
index 3557522d3c9eaa7e5e1fe8446bd71c9fae4c8861..69155131d8d72b837d68413270b6632f2cb9e4e0 100644
--- a/lib/omni_auth/request_forgery_protection.rb
+++ b/lib/omni_auth/request_forgery_protection.rb
@@ -1,66 +1,21 @@
 # Protects OmniAuth request phase against CSRF.
 
 module OmniAuth
-  # Based on ActionController::RequestForgeryProtection.
-  class RequestForgeryProtection
-    def initialize(env)
-      @env = env
-    end
-
-    def request
-      @request ||= ActionDispatch::Request.new(@env)
-    end
-
-    def session
-      request.session
-    end
-
-    def reset_session
-      request.reset_session
-    end
-
-    def params
-      request.params
-    end
-
-    def call
-      verify_authenticity_token
-    end
+  module RequestForgeryProtection
+    class Controller < ActionController::Base
+      protect_from_forgery with: :exception
 
-    def verify_authenticity_token
-      if !verified_request?
-        Rails.logger.warn "Can't verify CSRF token authenticity" if Rails.logger
-        handle_unverified_request
+      def index
+        head :ok
       end
     end
 
-    private
-
-    def protect_against_forgery?
-      ApplicationController.allow_forgery_protection
-    end
-
-    def request_forgery_protection_token
-      ApplicationController.request_forgery_protection_token
-    end
-
-    def forgery_protection_strategy
-      ApplicationController.forgery_protection_strategy
-    end
-
-    def verified_request?
-      !protect_against_forgery? || request.get? || request.head? ||
-        form_authenticity_token == params[request_forgery_protection_token] ||
-        form_authenticity_token == request.headers['X-CSRF-Token']
-    end
-
-    def handle_unverified_request
-      forgery_protection_strategy.new(self).handle_unverified_request
+    def self.app
+      @app ||= Controller.action(:index)
     end
 
-    # Sets the token value for the current session.
-    def form_authenticity_token
-      session[:_csrf_token] ||= SecureRandom.base64(32)
+    def self.call(env)
+      app.call(env)
     end
   end
 end
diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb
index 6762ca47c328cc0db82d85f7b220b5f356b1cc15..8c309efc7b8fc58c227fc21e51b01b01fa3efb27 100644
--- a/lib/rouge/formatters/html_gitlab.rb
+++ b/lib/rouge/formatters/html_gitlab.rb
@@ -39,7 +39,7 @@ module Rouge
           lineanchorsid: 'L',
           anchorlinenos: false,
           inline_theme: nil
-        )
+      )
         @nowrap = nowrap
         @cssclass = cssclass
         @linenos = linenos
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index f0a6c2b30e97ba2d82ac2c0a9dd9a38c1cc72a34..c5f07c8b508a948796901b0cf6bcc5024360ba71 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -33,12 +33,13 @@ app_user="git"
 app_root="/home/$app_user/gitlab"
 pid_path="$app_root/tmp/pids"
 socket_path="$app_root/tmp/sockets"
+rails_socket="$socket_path/gitlab.socket"
 web_server_pid_path="$pid_path/unicorn.pid"
 sidekiq_pid_path="$pid_path/sidekiq.pid"
 mail_room_enabled=false
 mail_room_pid_path="$pid_path/mail_room.pid"
 gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
-gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080"
+gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $rails_socket -documentRoot $app_root/public"
 gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
 shell_path="/bin/bash"
 
@@ -91,7 +92,7 @@ check_pids(){
 
 ## Called when we have started the two processes and are waiting for their pid files.
 wait_for_pids(){
-  # We are sleeping a bit here mostly because sidekiq is slow at writing it's pid
+  # We are sleeping a bit here mostly because sidekiq is slow at writing its pid
   i=0;
   while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || [ ! -f $gitlab_workhorse_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; }; do
     sleep 0.1;
@@ -107,7 +108,7 @@ wait_for_pids(){
 }
 
 # We use the pids in so many parts of the script it makes sense to always check them.
-# Only after start() is run should the pids change. Sidekiq sets it's own pid.
+# Only after start() is run should the pids change. Sidekiq sets its own pid.
 check_pids
 
 
@@ -289,7 +290,7 @@ stop_gitlab() {
   sleep 1
   # Cleaning up unused pids
   rm "$web_server_pid_path" 2>/dev/null
-  # rm "$sidekiq_pid_path" 2>/dev/null # Sidekiq seems to be cleaning up it's own pid.
+  # rm "$sidekiq_pid_path" 2>/dev/null # Sidekiq seems to be cleaning up its own pid.
   rm -f "$gitlab_workhorse_pid_path"
   if [ "$mail_room_enabled" = true ]; then
     rm "$mail_room_pid_path" 2>/dev/null
@@ -298,7 +299,7 @@ stop_gitlab() {
   print_status
 }
 
-## Prints the status of GitLab and it's components.
+## Prints the status of GitLab and its components.
 print_status() {
   check_status
   if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then
@@ -327,12 +328,12 @@ print_status() {
         printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n"
     fi
   fi
-  if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then
+  if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then
     printf "GitLab and all its components are \033[32mup and running\033[0m.\n"
   fi
 }
 
-## Tells unicorn to reload it's config and Sidekiq to restart
+## Tells unicorn to reload its config and Sidekiq to restart
 reload_gitlab(){
   exit_if_not_running
   if [ "$wpid" = "0" ];then
diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example
index 79ae8e0ae55abf2fb3fe76dd692b1b4fe9dd3462..1937ca582b0548939c42d2aca1bab39b45357b2a 100755
--- a/lib/support/init.d/gitlab.default.example
+++ b/lib/support/init.d/gitlab.default.example
@@ -9,11 +9,11 @@ RAILS_ENV="production"
 # The default is "git".
 app_user="git"
 
-# app_root defines the folder in which gitlab and it's components are installed.
+# app_root defines the folder in which gitlab and its components are installed.
 # The default is "/home/$app_user/gitlab"
 app_root="/home/$app_user/gitlab"
 
-# pid_path defines a folder in which the gitlab and it's components place their pids.
+# pid_path defines a folder in which the gitlab and its components place their pids.
 # This variable is also used below to define the relevant pids for the gitlab components.
 # The default is "$app_root/tmp/pids"
 pid_path="$app_root/tmp/pids"
@@ -36,7 +36,7 @@ gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
 # '-listenNetwork tcp -listenAddr localhost:8181'.
 # The -authBackend setting tells gitlab-workhorse where it can reach
 # Unicorn.
-gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080"
+gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $socket_path/gitlab.socket -documentRoot $app_root/public"
 gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
 
 # mail_room_enabled specifies whether mail_room, which is used to process incoming email, is enabled.
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 0cf5292b290967008fbe3f929d03347aad628325..fc5475c4eef881b2b67e8bcc231f622144b93fa0 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -10,34 +10,12 @@
 ## If you change this file in a Merge Request, please also create
 ## a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
 ##
-##################################
-##        CHUNKED TRANSFER      ##
-##################################
-##
-## It is a known issue that Git-over-HTTP requires chunked transfer encoding [0]
-## which is not supported by Nginx < 1.3.9 [1]. As a result, pushing a large object
-## with Git (i.e. a single large file) can lead to a 411 error. In theory you can get
-## around this by tweaking this configuration file and either:
-## - installing an old version of Nginx with the chunkin module [2] compiled in, or
-## - using a newer version of Nginx.
-##
-## At the time of writing we do not know if either of these theoretical solutions works.
-## As a workaround users can use Git over SSH to push large files.
-##
-## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99
-## [1] https://github.com/agentzh/chunkin-nginx-module#status
-## [2] https://github.com/agentzh/chunkin-nginx-module
-##
 ###################################
 ##         configuration         ##
 ###################################
 ##
 ## See installation.md#using-https for additional HTTPS configuration details.
 
-upstream gitlab {
-  server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
-}
-
 upstream gitlab-workhorse {
   server unix:/home/git/gitlab/tmp/sockets/gitlab-workhorse.socket fail_timeout=0;
 }
@@ -54,10 +32,6 @@ server {
   server_tokens off; ## Don't show the nginx version number, a security best practice
   root /home/git/gitlab/public;
 
-  ## Increase this if you want to upload large attachments
-  ## Or if you want to accept large git objects over http
-  client_max_body_size 20m;
-
   ## See app/controllers/application_controller.rb for headers set
 
   ## Individual nginx logs for this GitLab vhost
@@ -65,97 +39,8 @@ server {
   error_log   /var/log/nginx/gitlab_error.log;
 
   location / {
-    ## Serve static files from defined root folder.
-    ## @gitlab is a named location for the upstream fallback, see below.
-    try_files $uri /index.html $uri.html @gitlab;
-  }
-
-  ## We route uploads through GitLab to prevent XSS and enforce access control.
-  location /uploads/ {
-    ## If you use HTTPS make sure you disable gzip compression
-    ## to be safe against BREACH attack.
-    # gzip off;
-
-    ## https://github.com/gitlabhq/gitlabhq/issues/694
-    ## Some requests take more than 30 seconds.
-    proxy_read_timeout      300;
-    proxy_connect_timeout   300;
-    proxy_redirect          off;
-
-    proxy_set_header    Host                $http_host;
-    proxy_set_header    X-Real-IP           $remote_addr;
-    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
-    proxy_set_header    X-Forwarded-Proto   $scheme;
-    proxy_set_header    X-Frame-Options     SAMEORIGIN;
-
-    proxy_pass http://gitlab;
-  }
-
-  ## If a file, which is not found in the root folder is requested,
-  ## then the proxy passes the request to the upsteam (gitlab unicorn).
-  location @gitlab {
-    ## If you use HTTPS make sure you disable gzip compression
-    ## to be safe against BREACH attack.
-    # gzip off;
-
-    ## https://github.com/gitlabhq/gitlabhq/issues/694
-    ## Some requests take more than 30 seconds.
-    proxy_read_timeout      300;
-    proxy_connect_timeout   300;
-    proxy_redirect          off;
-
-    proxy_set_header    Host                $http_host;
-    proxy_set_header    X-Real-IP           $remote_addr;
-    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
-    proxy_set_header    X-Forwarded-Proto   $scheme;
-    proxy_set_header    X-Frame-Options     SAMEORIGIN;
-
-    proxy_pass http://gitlab;
-  }
-
-  location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects {
-    # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
-    error_page 418 = @gitlab-workhorse;
-    return 418;
-  }
-
-  location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
-    # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
-    error_page 418 = @gitlab-workhorse;
-    return 418;
-  }
-
-  location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
-    # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
-    error_page 418 = @gitlab-workhorse;
-    return 418;
-  }
-
-  location ~ ^/api/v3/projects/.*/repository/archive {
-    # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
-    error_page 418 = @gitlab-workhorse;
-    return 418;
-  }
-
-  # Build artifacts should be submitted to this location
-  location ~ ^/[\w\.-]+/[\w\.-]+/builds/download {
-      # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
-      error_page 418 = @gitlab-workhorse;
-      return 418;
-  }
-
-  # Build artifacts should be submitted to this location
-  location ~ /ci/api/v1/builds/[0-9]+/artifacts {
-      # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
-      error_page 418 = @gitlab-workhorse;
-      return 418;
-  }
-
-  location @gitlab-workhorse {
     client_max_body_size 0;
-    ## If you use HTTPS make sure you disable gzip compression
-    ## to be safe against BREACH attack.
-    # gzip off;
+    gzip off;
 
     ## https://github.com/gitlabhq/gitlabhq/issues/694
     ## Some requests take more than 30 seconds.
@@ -163,14 +48,7 @@ server {
     proxy_connect_timeout   300;
     proxy_redirect          off;
 
-    # Do not buffer Git HTTP responses
-    proxy_buffering off;
-
-    # The following settings only work with NGINX 1.7.11 or newer
-    #
-    # # Pass chunked request bodies to gitlab-workhorse as-is
-    # proxy_request_buffering off;
-    # proxy_http_version 1.1;
+    proxy_http_version 1.1;
 
     proxy_set_header    Host                $http_host;
     proxy_set_header    X-Real-IP           $remote_addr;
@@ -179,18 +57,4 @@ server {
 
     proxy_pass http://gitlab-workhorse;
   }
-
-  ## Enable gzip compression as per rails guide:
-  ## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression
-  ## WARNING: If you are using relative urls remove the block below
-  ## See config/application.rb under "Relative url support" for the list of
-  ## other files that need to be changed for relative url support
-  location ~ ^/(assets)/ {
-    root /home/git/gitlab/public;
-    gzip_static on; # to serve pre-gzipped version
-    expires max;
-    add_header Cache-Control public;
-  }
-
-  error_page 502 /502.html;
 }
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 31a651c87fd6ae14f20d5f7e72f392de96024ec6..1e5f85413ec6dd8d124c4795f3b9dbfa32acfa86 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -14,34 +14,12 @@
 ## If you change this file in a Merge Request, please also create
 ## a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
 ##
-##################################
-##        CHUNKED TRANSFER      ##
-##################################
-##
-## It is a known issue that Git-over-HTTP requires chunked transfer encoding [0]
-## which is not supported by Nginx < 1.3.9 [1]. As a result, pushing a large object
-## with Git (i.e. a single large file) can lead to a 411 error. In theory you can get
-## around this by tweaking this configuration file and either:
-## - installing an old version of Nginx with the chunkin module [2] compiled in, or
-## - using a newer version of Nginx.
-##
-## At the time of writing we do not know if either of these theoretical solutions works.
-## As a workaround users can use Git over SSH to push large files.
-##
-## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99
-## [1] https://github.com/agentzh/chunkin-nginx-module#status
-## [2] https://github.com/agentzh/chunkin-nginx-module
-##
 ###################################
 ##         configuration         ##
 ###################################
 ##
 ## See installation.md#using-https for additional HTTPS configuration details.
 
-upstream gitlab {
-  server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
-}
-
 upstream gitlab-workhorse {
   server unix:/home/git/gitlab/tmp/sockets/gitlab-workhorse.socket fail_timeout=0;
 }
@@ -56,12 +34,11 @@ server {
   listen [::]:80 ipv6only=on default_server;
   server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
   server_tokens off; ## Don't show the nginx version number, a security best practice
-  return 301 https://$server_name$request_uri;
+  return 301 https://$http_host$request_uri;
   access_log  /var/log/nginx/gitlab_access.log;
   error_log   /var/log/nginx/gitlab_error.log;
 }
 
-
 ## HTTPS host
 server {
   listen 0.0.0.0:443 ssl;
@@ -70,10 +47,6 @@ server {
   server_tokens off; ## Don't show the nginx version number, a security best practice
   root /home/git/gitlab/public;
 
-  ## Increase this if you want to upload large attachments
-  ## Or if you want to accept large git objects over http
-  client_max_body_size 20m;
-
   ## Strong SSL Security
   ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/
   ssl on;
@@ -110,98 +83,7 @@ server {
   error_log   /var/log/nginx/gitlab_error.log;
 
   location / {
-    ## Serve static files from defined root folder.
-    ## @gitlab is a named location for the upstream fallback, see below.
-    try_files $uri /index.html $uri.html @gitlab;
-  }
-
-  ## We route uploads through GitLab to prevent XSS and enforce access control.
-  location /uploads/ {
-    ## If you use HTTPS make sure you disable gzip compression
-    ## to be safe against BREACH attack.
-    gzip off;
-
-    ## https://github.com/gitlabhq/gitlabhq/issues/694
-    ## Some requests take more than 30 seconds.
-    proxy_read_timeout      300;
-    proxy_connect_timeout   300;
-    proxy_redirect          off;
-
-    proxy_set_header    Host                $http_host;
-    proxy_set_header    X-Real-IP           $remote_addr;
-    proxy_set_header    X-Forwarded-Ssl     on;
-    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
-    proxy_set_header    X-Forwarded-Proto   $scheme;
-    proxy_set_header    X-Frame-Options     SAMEORIGIN;
-
-    proxy_pass http://gitlab;
-  }
-
-  ## If a file, which is not found in the root folder is requested,
-  ## then the proxy passes the request to the upsteam (gitlab unicorn).
-  location @gitlab {
-    ## If you use HTTPS make sure you disable gzip compression
-    ## to be safe against BREACH attack.
-    gzip off;
-
-    ## https://github.com/gitlabhq/gitlabhq/issues/694
-    ## Some requests take more than 30 seconds.
-    proxy_read_timeout      300;
-    proxy_connect_timeout   300;
-    proxy_redirect          off;
-
-    proxy_set_header    Host                $http_host;
-    proxy_set_header    X-Real-IP           $remote_addr;
-    proxy_set_header    X-Forwarded-Ssl     on;
-    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
-    proxy_set_header    X-Forwarded-Proto   $scheme;
-    proxy_set_header    X-Frame-Options     SAMEORIGIN;
-
-    proxy_pass http://gitlab;
-  }
-
-  location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects {
-    # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
-    error_page 418 = @gitlab-workhorse;
-    return 418;
-  }
-
-  location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
-    # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
-    error_page 418 = @gitlab-workhorse;
-    return 418;
-  }
-
-  location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
-    # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
-    error_page 418 = @gitlab-workhorse;
-    return 418;
-  }
-
-  location ~ ^/api/v3/projects/.*/repository/archive {
-    # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
-    error_page 418 = @gitlab-workhorse;
-    return 418;
-  }
-
-  # Build artifacts should be submitted to this location
-  location ~ ^/[\w\.-]+/[\w\.-]+/builds/download {
-      # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
-      error_page 418 = @gitlab-workhorse;
-      return 418;
-  }
-
-  # Build artifacts should be submitted to this location
-  location ~ /ci/api/v1/builds/[0-9]+/artifacts {
-      # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
-      error_page 418 = @gitlab-workhorse;
-      return 418;
-  }
-
-  location @gitlab-workhorse {
     client_max_body_size 0;
-    ## If you use HTTPS make sure you disable gzip compression
-    ## to be safe against BREACH attack.
     gzip off;
 
     ## https://github.com/gitlabhq/gitlabhq/issues/694
@@ -210,14 +92,7 @@ server {
     proxy_connect_timeout   300;
     proxy_redirect          off;
 
-    # Do not buffer Git HTTP responses
-    proxy_buffering off;
-
-    # The following settings only work with NGINX 1.7.11 or newer
-    #
-    # # Pass chunked request bodies to gitlab-workhorse as-is
-    # proxy_request_buffering off;
-    # proxy_http_version 1.1;
+    proxy_http_version 1.1;
 
     proxy_set_header    Host                $http_host;
     proxy_set_header    X-Real-IP           $remote_addr;
@@ -226,18 +101,4 @@ server {
     proxy_set_header    X-Forwarded-Proto   $scheme;
     proxy_pass http://gitlab-workhorse;
   }
-
-  ## Enable gzip compression as per rails guide:
-  ## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression
-  ## WARNING: If you are using relative urls remove the block below
-  ## See config/application.rb under "Relative url support" for the list of
-  ## other files that need to be changed for relative url support
-  location ~ ^/(assets)/ {
-    root /home/git/gitlab/public;
-    gzip_static on; # to serve pre-gzipped version
-    expires max;
-    add_header Cache-Control public;
-  }
-
-  error_page 502 /502.html;
 }
diff --git a/lib/tasks/ci/schedule_builds.rake b/lib/tasks/ci/schedule_builds.rake
deleted file mode 100644
index 49435504c67c77c75d58a21448e100ff932c89fa..0000000000000000000000000000000000000000
--- a/lib/tasks/ci/schedule_builds.rake
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace :ci do
-  desc "GitLab CI | Clean running builds"
-  task schedule_builds: :environment do
-    Ci::Scheduler.new.perform
-  end
-end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index a25fac62cfc1a392158574aca60d70669dfb9977..0469c5a61c38b980c5ef50367916abcc1e8f6049 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -331,7 +331,7 @@ namespace :gitlab do
     end
 
     def check_redis_version
-      min_redis_version = "2.4.0"
+      min_redis_version = "2.8.0"
       print "Redis version >= #{min_redis_version}? ... "
 
       redis_version = run(%W(redis-cli --version))
@@ -822,10 +822,27 @@ namespace :gitlab do
 
       namespace_dirs.each do |namespace_dir|
         repo_dirs = Dir.glob(File.join(namespace_dir, '*'))
-        repo_dirs.each do |dir|
-          puts "\nChecking repo at #{dir}"
-          system(*%W(#{Gitlab.config.git.bin_path} fsck), chdir: dir)
-        end
+        repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) }
+      end
+    end
+  end
+
+  namespace :user do
+    desc "GitLab | Check the integrity of a specific user's repositories"
+    task :check_repos, [:username] => :environment do |t, args|
+      username = args[:username] || prompt("Check repository integrity for which username? ".blue)
+      user = User.find_by(username: username)
+      if user
+        repo_dirs = user.authorized_projects.map do |p|
+                      File.join(
+                        Gitlab.config.gitlab_shell.repos_path,
+                        "#{p.path_with_namespace}.git"
+                      )
+                    end
+
+        repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) }
+      else
+        puts "\nUser '#{username}' not found".red
       end
     end
   end
@@ -952,4 +969,35 @@ namespace :gitlab do
       false
     end
   end
+
+  def check_repo_integrity(repo_dir)
+    puts "\nChecking repo at #{repo_dir.yellow}"
+
+    git_fsck(repo_dir)
+    check_config_lock(repo_dir)
+    check_ref_locks(repo_dir)
+  end
+
+  def git_fsck(repo_dir)
+    puts "Running `git fsck`".yellow
+    system(*%W(#{Gitlab.config.git.bin_path} fsck), chdir: repo_dir)
+  end
+
+  def check_config_lock(repo_dir)
+    config_exists = File.exist?(File.join(repo_dir,'config.lock'))
+    config_output = config_exists ? 'yes'.red : 'no'.green
+    puts "'config.lock' file exists?".yellow + " ... #{config_output}"
+  end
+
+  def check_ref_locks(repo_dir)
+    lock_files = Dir.glob(File.join(repo_dir,'refs/heads/*.lock'))
+    if lock_files.present?
+      puts "Ref lock files exist:".red
+      lock_files.each do |lock_file|
+        puts "  #{lock_file}"
+      end
+    else
+      puts "No ref lock files exist".green
+    end
+  end
 end
diff --git a/lib/tasks/gitlab/git.rake b/lib/tasks/gitlab/git.rake
new file mode 100644
index 0000000000000000000000000000000000000000..65ee430d550ff54c144cfd1d0532f5b74c689e64
--- /dev/null
+++ b/lib/tasks/gitlab/git.rake
@@ -0,0 +1,55 @@
+namespace :gitlab do
+  namespace :git do
+
+    desc "GitLab | Git | Repack"
+    task repack: :environment do
+      failures = perform_git_cmd(%W(git repack -a --quiet), "Repacking repo")
+      if failures.empty?
+        puts "Done".green
+      else
+        output_failures(failures)
+      end
+    end
+
+    desc "GitLab | Git | Run garbage collection on all repos"
+    task gc: :environment do
+      failures = perform_git_cmd(%W(git gc --auto --quiet), "Garbage Collecting")
+      if failures.empty?
+        puts "Done".green
+      else
+        output_failures(failures)
+      end
+    end
+    
+    desc "GitLab | Git | Prune all repos"
+    task prune: :environment do
+      failures = perform_git_cmd(%W(git prune), "Git Prune")
+      if failures.empty?
+        puts "Done".green
+      else
+        output_failures(failures)
+      end
+    end
+
+    def perform_git_cmd(cmd, message)
+      puts "Starting #{message} on all repositories"
+
+      failures = []
+      all_repos do |repo|
+        if system(*cmd, chdir: repo)
+          puts "Performed #{message} at #{repo}"
+        else
+          failures << repo
+        end
+      end
+
+      failures
+    end
+
+    def output_failures(failures)
+      puts "The following repositories reported errors:".red
+      failures.each { |f| puts "- #{f}" }
+    end
+
+  end
+end
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index c1ee271ae2b23ebeb5582e3c1fe6a2e0fc273966..1c04f47f08ffe2b5e8239b0218e4d148b2740711 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -64,6 +64,8 @@ namespace :gitlab do
 
           if project.persisted?
             puts " * Created #{project.name} (#{repo_path})".green
+            project.update_repository_size
+            project.update_commit_count
           else
             puts " * Failed trying to create #{project.name} (#{repo_path})".red
             puts "   Errors: #{project.errors.messages}".red
diff --git a/lib/tasks/gitlab/list_repos.rake b/lib/tasks/gitlab/list_repos.rake
new file mode 100644
index 0000000000000000000000000000000000000000..c7596e7abcb72d33b1403f84c37a4f43dcf8362c
--- /dev/null
+++ b/lib/tasks/gitlab/list_repos.rake
@@ -0,0 +1,17 @@
+namespace :gitlab do
+  task list_repos: :environment do
+    scope = Project
+    if ENV['SINCE']
+      date = Time.parse(ENV['SINCE'])
+      warn "Listing repositories with activity or changes since #{date}"
+      project_ids = Project.where('last_activity_at > ? OR updated_at > ?', date, date).pluck(:id).sort
+      namespace_ids = Namespace.where(['updated_at > ?', date]).pluck(:id).sort
+      scope = scope.where('id IN (?) OR namespace_id in (?)', project_ids, namespace_ids)
+    end
+    scope.find_each do |project|
+      base = File.join(Gitlab.config.gitlab_shell.repos_path, project.path_with_namespace)
+      puts base + '.git'
+      puts base + '.wiki.git'
+    end
+  end
+end
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index c95b6540ebc6e2ce4315f2a74b2d3d2cf050b383..ebe516ec879c71e67dff706a173d110ec926c91b 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -2,16 +2,6 @@ module Gitlab
   class TaskAbortedByUserError < StandardError; end
 end
 
-unless STDOUT.isatty
-  module Colored
-    extend self
-
-    def colorize(string, options={})
-      string
-    end
-  end
-end
-
 namespace :gitlab do
 
   # Ask if the user wants to continue
@@ -103,7 +93,7 @@ namespace :gitlab do
       gitlab_user = Gitlab.config.gitlab.user
       current_user = run(%W(whoami)).chomp
       unless current_user == gitlab_user
-        puts "#{Colored.color(:black)+Colored.color(:on_yellow)} Warning #{Colored.extra(:clear)}"
+        puts " Warning ".colorize(:black).on_yellow
         puts "  You are running as user #{current_user.magenta}, we hope you know what you are doing."
         puts "  Things may work\/fail for the wrong reasons."
         puts "  For correct results you should run this as user #{gitlab_user.magenta}."
@@ -128,4 +118,12 @@ namespace :gitlab do
       false
     end
   end
+
+  def all_repos
+    IO.popen(%W(find #{Gitlab.config.gitlab_shell.repos_path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find|
+      find.each_line do |path|
+        yield path.chomp
+      end
+    end
+  end
 end
diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake
index 365ff2defd41e3bc9c7a65269e9e6be1376f7dec..0985ef3a669e5d86ccf42ed5dacb104377008704 100644
--- a/lib/tasks/spec.rake
+++ b/lib/tasks/spec.rake
@@ -19,6 +19,33 @@ namespace :spec do
     run_commands(cmds)
   end
 
+  desc 'GitLab | Rspec | Run model specs'
+  task :models do
+    cmds = [
+      %W(rake gitlab:setup),
+      %W(rspec spec --tag @models)
+    ]
+    run_commands(cmds)
+  end
+
+  desc 'GitLab | Rspec | Run service specs'
+  task :services do
+    cmds = [
+      %W(rake gitlab:setup),
+      %W(rspec spec --tag @services)
+    ]
+    run_commands(cmds)
+  end
+
+  desc 'GitLab | Rspec | Run lib specs'
+  task :lib do
+    cmds = [
+      %W(rake gitlab:setup),
+      %W(rspec spec --tag @lib)
+    ]
+    run_commands(cmds)
+  end
+
   desc 'GitLab | Rspec | Run benchmark specs'
   task :benchmark do
     cmds = [
@@ -32,7 +59,7 @@ namespace :spec do
   task :other do
     cmds = [
       %W(rake gitlab:setup),
-      %W(rspec spec --tag ~@api --tag ~@feature --tag ~@benchmark)
+      %W(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@lib --tag ~@services --tag ~@benchmark)
     ]
     run_commands(cmds)
   end
diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake
index d5a96fd38f4776459c55ab2d7779625f14e92a23..3acfc6e207505317c3c494960d2bfdd78dce41d4 100644
--- a/lib/tasks/spinach.rake
+++ b/lib/tasks/spinach.rake
@@ -1,11 +1,31 @@
 Rake::Task["spinach"].clear if Rake::Task.task_defined?('spinach')
 
 namespace :spinach do
+  namespace :project do
+    desc "GitLab | Spinach | Run project commits, issues and merge requests spinach features"
+    task :half do
+      cmds = [
+        %W(rake gitlab:setup),
+        %W(spinach --tags @project_commits,@project_issues,@project_merge_requests),
+      ]
+      run_commands(cmds)
+    end
+
+    desc "GitLab | Spinach | Run remaining project spinach features"
+    task :rest do
+      cmds = [
+        %W(rake gitlab:setup),
+        %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets,~@project_commits,~@project_issues,~@project_merge_requests),
+      ]
+      run_commands(cmds)
+    end
+  end
+
   desc "GitLab | Spinach | Run project spinach features"
   task :project do
     cmds = [
       %W(rake gitlab:setup),
-      %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets,~@commits),
+      %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets),
     ]
     run_commands(cmds)
   end
@@ -14,7 +34,7 @@ namespace :spinach do
   task :other do
     cmds = [
       %W(rake gitlab:setup),
-      %W(spinach --tags @admin,@dashboard,@profile,@public,@snippets,@commits),
+      %W(spinach --tags @admin,@dashboard,@profile,@public,@snippets),
     ]
     run_commands(cmds)
   end
@@ -33,4 +53,4 @@ def run_commands(cmds)
   cmds.each do |cmd|
     system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd) or raise("#{cmd} failed!")
   end
-end
\ No newline at end of file
+end
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index dca5e1c5db3b141d7e0f34c4dd0fbc48f2f7e452..119cc90fc1ebde8edb6aa5d5289aae77ac35cffb 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 if [ -f /.dockerinit ]; then
-    wget -q http://ftp.de.debian.org/debian/pool/main/p/phantomjs/phantomjs_1.9.0-1+b1_amd64.deb
-    dpkg -i phantomjs_1.9.0-1+b1_amd64.deb
+    wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
+    dpkg -i phantomjs_1.9.8-0jessie_amd64.deb
 
     apt-get update -qq
     apt-get install -y -qq libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client
diff --git a/shared/lfs-objects/.gitkeep b/shared/lfs-objects/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb b/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
index 34cd9f7e4eb398bd64d8d65226d15bc7488767c4..3855763b200b8ffafd01adc0f7d324f117c3b8a9 100644
--- a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
+++ b/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Markdown::ReferenceFilter, benchmark: true do
+describe Banzai::Filter::ReferenceFilter, benchmark: true do
   let(:input) do
     html = <<-EOF
 <p>Hello @alice and @bob, how are you doing today?</p>
diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb
index 0faab8d7ff0b2e3885325bf5a9f48947bcc55111..15824a1c67f2149d5e02c8d75c6fe00a92c9cbc2 100644
--- a/spec/controllers/abuse_reports_controller_spec.rb
+++ b/spec/controllers/abuse_reports_controller_spec.rb
@@ -18,27 +18,31 @@ describe AbuseReportsController do
       end
 
       it "sends a notification email" do
-        post :create,
-          abuse_report: {
-            user_id: user.id,
-            message: message
-          }
-
-        email = ActionMailer::Base.deliveries.last
-
-        expect(email.to).to eq([admin_email])
-        expect(email.subject).to include(user.username)
-        expect(email.text_part.body).to include(message)
-      end
-
-      it "saves the abuse report" do
-        expect do
+        perform_enqueued_jobs do
           post :create,
             abuse_report: {
               user_id: user.id,
               message: message
             }
-        end.to change { AbuseReport.count }.by(1)
+
+          email = ActionMailer::Base.deliveries.last
+
+          expect(email.to).to eq([admin_email])
+          expect(email.subject).to include(user.username)
+          expect(email.text_part.body).to include(message)
+        end
+      end
+
+      it "saves the abuse report" do
+        perform_enqueued_jobs do
+          expect do
+            post :create,
+              abuse_report: {
+                user_id: user.id,
+                message: message
+              }
+          end.to change { AbuseReport.count }.by(1)
+        end
       end
     end
 
diff --git a/spec/controllers/admin/impersonation_controller_spec.rb b/spec/controllers/admin/impersonation_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d7a7ba1c5b6601194dac6a17ca0e7e58e3c40e00
--- /dev/null
+++ b/spec/controllers/admin/impersonation_controller_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Admin::ImpersonationController do
+  let(:admin) { create(:admin) }
+
+  before do
+    sign_in(admin)
+  end
+
+  describe 'CREATE #impersonation when blocked' do
+    let(:blocked_user) { create(:user, state: :blocked) }
+
+    it 'does not allow impersonation' do
+      post :create, id: blocked_user.username
+
+      expect(flash[:alert]).to eq 'You cannot impersonate a blocked user'
+    end
+  end
+end
diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb
index bb3d87f384081f488d4fd500e837be9a72d75661..7793bf1e42170af64133c6de6d57a5447220dae3 100644
--- a/spec/controllers/commit_controller_spec.rb
+++ b/spec/controllers/commit_controller_spec.rb
@@ -69,6 +69,21 @@ describe Projects::CommitController do
 
         expect(response.body).to start_with("diff --git")
       end
+      
+      it "should really only be a git diff without whitespace changes" do
+        get(:show,
+            namespace_id: project.namespace.to_param,
+            project_id: project.to_param,
+            id: '66eceea0db202bb39c4e445e8ca28689645366c5',
+            # id: commit.id,
+            format: format,
+            w: 1)
+
+        expect(response.body).to start_with("diff --git")
+        # without whitespace option, there are more than 2 diff_splits
+        diff_splits = assigns(:diffs)[0].diff.split("\n")
+        expect(diff_splits.length).to be <= 2
+      end
     end
 
     describe "as patch" do
@@ -95,6 +110,26 @@ describe Projects::CommitController do
         expect(response.body).to match(/^diff --git/)
       end
     end
+
+    context 'commit that removes a submodule' do
+      render_views
+
+      let(:fork_project) { create(:forked_project_with_submodules) }
+      let(:commit) { fork_project.commit('remove-submodule') }
+
+      before do
+        fork_project.team << [user, :master]
+      end
+
+      it 'renders it' do
+        get(:show,
+            namespace_id: fork_project.namespace.to_param,
+            project_id: fork_project.to_param,
+            id: commit.id)
+
+        expect(response).to be_success
+      end
+    end
   end
 
   describe "#branches" do
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eb0c6ac6d806b1d9d3d0d83a32794a98001991ed
--- /dev/null
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Groups::MilestonesController do
+  let(:group) { create(:group) }
+  let(:project) { create(:project, group: group) }
+  let(:project2) { create(:empty_project, group: group) }
+  let(:user)    { create(:user) }
+  let(:title) { '肯定不是中文的问题' }
+
+  before do
+    sign_in(user)
+    group.add_owner(user)
+    project.team << [user, :master]
+    controller.instance_variable_set(:@group, group)
+  end
+
+  describe "#create" do
+    it "should create group milestone with Chinese title" do
+      post :create,
+           group_id: group.id,
+           milestone: { project_ids: [project.id, project2.id], title: title }
+
+      expect(response).to redirect_to(group_milestone_path(group, title.to_slug.to_s, title: title))
+      expect(Milestone.where(title: title).count).to eq(2)
+    end
+  end
+end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 3e5e1fa87ae710b80cbddc2c40d63e7e8ecb56af..6aaec224f6eeff04ceaed2106978fa40201186ab 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -10,6 +10,30 @@ describe Projects::MergeRequestsController do
     project.team << [user, :master]
   end
 
+  describe '#new' do
+    context 'merge request that removes a submodule' do
+      render_views
+
+      let(:fork_project) { create(:forked_project_with_submodules) }
+
+      before do
+        fork_project.team << [user, :master]
+      end
+
+      it 'renders it' do
+        get :new,
+            namespace_id: fork_project.namespace.to_param,
+            project_id: fork_project.to_param,
+            merge_request: {
+              source_branch: 'remove-submodule',
+              target_branch: 'master'
+            }
+
+        expect(response).to be_success
+      end
+    end
+  end
+
   describe "#show" do
     shared_examples "export merge as" do |format|
       it "should generally work" do
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 8127efabe6e3e15e24ade7d24ec578f1f4b1eeda..d173bb350f1c66c0615d3114ffa5a4290e37ce87 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -5,7 +5,7 @@ describe Projects::MilestonesController do
   let(:user)    { create(:user) }
   let(:milestone) { create(:milestone, project: project) }
   let(:issue) { create(:issue, project: project, milestone: milestone) }
-  let(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
+  let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
 
   before do
     sign_in(user)
@@ -15,10 +15,9 @@ describe Projects::MilestonesController do
 
   describe "#destroy" do
     it "should remove milestone" do
-      merge_request.reload
       expect(issue.milestone_id).to eq(milestone.id)
 
-      delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.id, format: :js
+      delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid, format: :js
       expect(response).to be_success
 
       expect(Event.first.action).to eq(Event::DESTROYED)
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index c114f3420212fa740896077efc08fe9be501b3ec..1caa476d37d92c622bfa1d72f5af135e5051e881 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -33,5 +33,39 @@ describe Projects::RawController do
         expect(response.header['Content-Type']).to eq('image/jpeg')
       end
     end
+
+    context 'lfs object' do
+      let(:id) { 'be93687/files/lfs/lfs_object.iso' }
+      let!(:lfs_object) { create(:lfs_object, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', size: '1575078') }
+
+      context 'when project has access' do
+        before do
+          public_project.lfs_objects << lfs_object
+          allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
+          allow(controller).to receive(:send_file) { controller.render nothing: true }
+        end
+
+        it 'serves the file' do
+          expect(controller).to receive(:send_file).with("#{Gitlab.config.shared.path}/lfs-objects/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: "lfs_object.iso", disposition: 'attachment')
+          get(:show,
+              namespace_id: public_project.namespace.to_param,
+              project_id: public_project.to_param,
+              id: id)
+
+          expect(response.status).to eq(200)
+        end
+      end
+
+      context 'when project does not have access' do
+        it 'does not serve the file' do
+          get(:show,
+              namespace_id: public_project.namespace.to_param,
+              project_id: public_project.to_param,
+              id: id)
+
+          expect(response.status).to eq(404)
+        end
+      end
+    end
   end
 end
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index a474574c6e5a40284b9a57e9d6ad9ffdd44b81a5..e74731c9ed82b07093aa3718306dbeac5ce9bebe 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -98,7 +98,7 @@ describe Projects::TreeController do
            project_id: project.to_param,
            id: 'master',
            dir_name: path,
-           new_branch: target_branch,
+           target_branch: target_branch,
            commit_message: 'Test commit message')
     end
 
@@ -108,8 +108,8 @@ describe Projects::TreeController do
 
       it 'redirects to the new directory' do
         expect(subject).
-            to redirect_to("/#{project.path_with_namespace}/blob/#{target_branch}/#{path}")
-        expect(flash[:notice]).to eq('The directory has been successfully created')
+            to redirect_to("/#{project.path_with_namespace}/tree/#{target_branch}/#{path}")
+        expect(flash[:notice]).to eq('The directory has been successfully created.')
       end
     end
 
@@ -119,7 +119,7 @@ describe Projects::TreeController do
 
       it 'does not allow overwriting of existing files' do
         expect(subject).
-            to redirect_to("/#{project.path_with_namespace}/blob/master")
+            to redirect_to("/#{project.path_with_namespace}/tree/master")
         expect(flash[:alert]).to eq('Directory already exists as a file')
       end
     end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 4bb47c6b02559c73c815f659801c16060aa13b80..665526fde9312c5f16ef0aab56f1dedeeb1552e5 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -88,6 +88,22 @@ describe ProjectsController do
     end
   end
 
+  describe "#destroy" do
+    let(:admin) { create(:admin) }
+
+    it "redirects to the dashboard" do
+      controller.instance_variable_set(:@project, project)
+      sign_in(admin)
+
+      orig_id = project.id
+      delete :destroy, namespace_id: project.namespace.path, id: project.path
+
+      expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound)
+      expect(response.status).to eq(302)
+      expect(response).to redirect_to(dashboard_projects_path)
+    end
+  end
+
   describe "POST #toggle_star" do
     it "toggles star if user is signed in" do
       sign_in(user)
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index e9b823c523c0b5038843a0e9489a4f64e41f3907..b3dcb52c5003bcd79bc187805f0766c7e7ca4992 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -115,4 +115,119 @@ describe SnippetsController do
       end
     end
   end
+
+  describe 'GET #raw' do
+    let(:user) { create(:user) }
+
+    context 'when the personal snippet is private' do
+      let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
+
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
+
+        context 'when signed in user is not the author' do
+          let(:other_author) { create(:author) }
+          let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
+
+          it 'responds with status 404' do
+            get :raw, id: other_personal_snippet.to_param
+
+            expect(response.status).to eq(404)
+          end
+        end
+
+        context 'when signed in user is the author' do
+          it 'renders the raw snippet' do
+            get :raw, id: personal_snippet.to_param
+
+            expect(assigns(:snippet)).to eq(personal_snippet)
+            expect(response.status).to eq(200)
+          end
+        end
+      end
+
+      context 'when not signed in' do
+        it 'redirects to the sign in page' do
+          get :raw, id: personal_snippet.to_param
+
+          expect(response).to redirect_to(new_user_session_path)
+        end
+      end
+    end
+
+    context 'when the personal snippet is internal' do
+      let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
+
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
+
+        it 'renders the raw snippet' do
+          get :raw, id: personal_snippet.to_param
+
+          expect(assigns(:snippet)).to eq(personal_snippet)
+          expect(response.status).to eq(200)
+        end
+      end
+
+      context 'when not signed in' do
+        it 'redirects to the sign in page' do
+          get :raw, id: personal_snippet.to_param
+
+          expect(response).to redirect_to(new_user_session_path)
+        end
+      end
+    end
+
+    context 'when the personal snippet is public' do
+      let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
+
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
+
+        it 'renders the raw snippet' do
+          get :raw, id: personal_snippet.to_param
+
+          expect(assigns(:snippet)).to eq(personal_snippet)
+          expect(response.status).to eq(200)
+        end
+      end
+
+      context 'when not signed in' do
+        it 'renders the raw snippet' do
+          get :raw, id: personal_snippet.to_param
+
+          expect(assigns(:snippet)).to eq(personal_snippet)
+          expect(response.status).to eq(200)
+        end
+      end
+    end
+
+    context 'when the personal snippet does not exist' do
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
+
+        it 'responds with status 404' do
+          get :raw, id: 'doesntexist'
+
+          expect(response.status).to eq(404)
+        end
+      end
+
+      context 'when not signed in' do
+        it 'responds with status 404' do
+          get :raw, id: 'doesntexist'
+
+          expect(response.status).to eq(404)
+        end
+      end
+    end
+  end
 end
diff --git a/spec/factories.rb b/spec/factories.rb
index 4bf93adabe27c31e5977daa3eb85726ce88e2a17..d6b4efa9a03d78b858ccf43b2e2d0cc54e57c9c0 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -43,7 +43,8 @@ FactoryGirl.define do
       end
 
       after(:create) do |user, evaluator|
-        user.identities << create(:identity,
+        user.identities << create(
+          :identity,
           provider: evaluator.provider,
           extern_uid: evaluator.extern_uid
         )
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 2fcd70182b9765414d1fc6c3dd2ab1c2c5ef5de8..f76e826f138c58c297a5fb5f9b9d21dc7d287edf 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -42,6 +42,10 @@ FactoryGirl.define do
 
     commit factory: :ci_commit
 
+    after(:build) do |build, evaluator|
+      build.project = build.commit.project
+    end
+
     factory :ci_not_started_build do
       started_at nil
       finished_at nil
diff --git a/spec/factories/ci/commits.rb b/spec/factories/ci/commits.rb
index 79e000b7ccb1c164e519f93d70904303a3ff3c51..b42cafa518ac905c15f4c8f99ac3610cb11ee489 100644
--- a/spec/factories/ci/commits.rb
+++ b/spec/factories/ci/commits.rb
@@ -2,17 +2,18 @@
 #
 # Table name: commits
 #
-#  id           :integer          not null, primary key
-#  project_id   :integer
-#  ref          :string(255)
-#  sha          :string(255)
-#  before_sha   :string(255)
-#  push_data    :text
-#  created_at   :datetime
-#  updated_at   :datetime
-#  tag          :boolean          default(FALSE)
-#  yaml_errors  :text
-#  committed_at :datetime
+#  id             :integer          not null, primary key
+#  project_id     :integer
+#  ref            :string(255)
+#  sha            :string(255)
+#  before_sha     :string(255)
+#  push_data      :text
+#  created_at     :datetime
+#  updated_at     :datetime
+#  tag            :boolean          default(FALSE)
+#  yaml_errors    :text
+#  committed_at   :datetime
+#  gl_project_id  :integer
 #
 
 # Read about factories at https://github.com/thoughtbot/factory_girl
@@ -20,7 +21,7 @@ FactoryGirl.define do
   factory :ci_empty_commit, class: Ci::Commit do
     sha '97de212e80737a608d939f648d959671fb0a0142'
 
-    gl_project factory: :empty_project
+    project factory: :empty_project
 
     factory :ci_commit_without_jobs do
       after(:build) do |commit|
diff --git a/spec/factories/ci/events.rb b/spec/factories/ci/events.rb
deleted file mode 100644
index 9638618a400aea1afb3fa9ba8f9f38eafca8ee9e..0000000000000000000000000000000000000000
--- a/spec/factories/ci/events.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# == Schema Information
-#
-# Table name: events
-#
-#  id          :integer          not null, primary key
-#  project_id  :integer
-#  user_id     :integer
-#  is_admin    :integer
-#  description :text
-#  created_at  :datetime
-#  updated_at  :datetime
-#
-
-FactoryGirl.define do
-  factory :ci_event, class: Ci::Event do
-    sequence :description do |n|
-      "updated project settings#{n}"
-    end
-
-    factory :ci_admin_event do
-      is_admin true
-    end
-  end
-end
diff --git a/spec/factories/ci/projects.rb b/spec/factories/ci/projects.rb
deleted file mode 100644
index 11cb8c9eeaa25647707ff80364b7b8f9a4c7730d..0000000000000000000000000000000000000000
--- a/spec/factories/ci/projects.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# == Schema Information
-#
-# Table name: projects
-#
-#  id                       :integer          not null, primary key
-#  name                     :string(255)      not null
-#  timeout                  :integer          default(3600), not null
-#  created_at               :datetime
-#  updated_at               :datetime
-#  token                    :string(255)
-#  default_ref              :string(255)
-#  path                     :string(255)
-#  always_build             :boolean          default(FALSE), not null
-#  polling_interval         :integer
-#  public                   :boolean          default(FALSE), not null
-#  ssh_url_to_repo          :string(255)
-#  gitlab_id                :integer
-#  allow_git_fetch          :boolean          default(TRUE), not null
-#  email_recipients         :string(255)      default(""), not null
-#  email_add_pusher         :boolean          default(TRUE), not null
-#  email_only_broken_builds :boolean          default(TRUE), not null
-#  skip_refs                :string(255)
-#  coverage_regex           :string(255)
-#  shared_runners_enabled   :boolean          default(FALSE)
-#  generated_yaml_config    :text
-#
-
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
-FactoryGirl.define do
-  factory :ci_project_without_token, class: Ci::Project do
-    default_ref 'master'
-
-    shared_runners_enabled false
-
-    factory :ci_project do
-      token 'iPWx6WM4lhHNedGfBpPJNP'
-    end
-
-    initialize_with do
-      # TODO:
-      # this is required, because builds_enabled is initialized when Project is created
-      # and this create gitlab_ci_project if builds is set to true
-      # here we take created gitlab_ci_project and update it's attributes
-      ci_project = create(:empty_project).ensure_gitlab_ci_project
-      ci_project.update_attributes(attributes)
-      ci_project
-    end
-  end
-end
diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb
index 3aa14ca434db06017a0b232540b0206ea41432cc..008d1c5d96172f664518b7bb885e6c1208cad6a9 100644
--- a/spec/factories/ci/runner_projects.rb
+++ b/spec/factories/ci/runner_projects.rb
@@ -14,6 +14,6 @@
 FactoryGirl.define do
   factory :ci_runner_project, class: Ci::RunnerProject do
     runner_id 1
-    project_id 1
+    gl_project_id 1
   end
 end
diff --git a/spec/factories/ci/web_hook.rb b/spec/factories/ci/web_hook.rb
deleted file mode 100644
index 40d878ecb3ced270962e2b885b5b58d4d6fb1806..0000000000000000000000000000000000000000
--- a/spec/factories/ci/web_hook.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-FactoryGirl.define do
-  factory :ci_web_hook, class: Ci::WebHook do
-    sequence(:url) { FFaker::Internet.uri('http') }
-    project factory: :ci_project
-  end
-end
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 52de437052d99e4047900e64560c5771a06afb13..8898b71e2a3724182a4e70d4e237102ad97560f8 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -5,7 +5,7 @@ FactoryGirl.define do
     name 'default'
     status 'success'
     description 'commit status'
-    commit factory: :ci_commit
+    commit factory: :ci_commit_with_one_job
 
     factory :generic_commit_status, class: GenericCommitStatus do
       name 'generic'
diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb
index 7fb2d77ca3201574b83cf8d8c6a70a5b4c507661..2da107ba24b496405fd140094cc689253dfc933d 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -1,3 +1,15 @@
+# == Schema Information
+#
+# Table name: lfs_objects
+#
+#  id         :integer          not null, primary key
+#  oid        :string(255)      not null
+#  size       :integer          not null
+#  created_at :datetime
+#  updated_at :datetime
+#  file       :string(255)
+#
+
 # Read about factories at https://github.com/thoughtbot/factory_girl
 
 FactoryGirl.define do
diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb
index 93de6607df84a5fbfc4f977b56aabea1fcaec288..3772236a77af91e318f2e72297a73c4ec7d08eb3 100644
--- a/spec/factories/lfs_objects_projects.rb
+++ b/spec/factories/lfs_objects_projects.rb
@@ -1,3 +1,14 @@
+# == Schema Information
+#
+# Table name: lfs_objects_projects
+#
+#  id            :integer          not null, primary key
+#  lfs_object_id :integer          not null
+#  project_id    :integer          not null
+#  created_at    :datetime
+#  updated_at    :datetime
+#
+
 # Read about factories at https://github.com/thoughtbot/factory_girl
 
 FactoryGirl.define do
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 729a49c9f7215326425e75a4649323fa480b7ff9..5b4d7f41bc4a76d92454abd47474b792d8841dbe 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -65,6 +65,11 @@ FactoryGirl.define do
       target_branch "master"
     end
 
+    trait :merge_when_build_succeeds do
+      merge_when_build_succeeds true
+      merge_user author
+    end
+
     factory :closed_merge_request, traits: [:closed]
     factory :reopened_merge_request, traits: [:reopened]
     factory :merge_request_with_diffs, traits: [:with_diffs]
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 9d777ddfccda397dc4cfe2729a624800de69fab7..35a20adeef3bd01871a4bc2b684f95338310dd95 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -16,6 +16,7 @@
 #  system        :boolean          default(FALSE), not null
 #  st_diff       :text
 #  updated_by_id :integer
+#  is_award      :boolean          default(FALSE), not null
 #
 
 require_relative '../support/repo_helpers'
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 1d500a11ad773df167c4b6d7c8c5ad459b29b6ad..112213377ff083480afe6f5d13a4a01f2fd227b8 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -28,6 +28,7 @@
 #  import_type            :string(255)
 #  import_source          :string(255)
 #  commit_count           :integer          default(0)
+#  import_error           :text
 #
 
 FactoryGirl.define do
diff --git a/spec/features/ci/admin/builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
similarity index 69%
rename from spec/features/ci/admin/builds_spec.rb
rename to spec/features/admin/admin_builds_spec.rb
index 623d466c67b2a09f0078def8f12e8371bb1c310c..72764b1629d2a71f5e36b482f59213097d8d3d81 100644
--- a/spec/features/ci/admin/builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -5,17 +5,16 @@ describe "Admin Builds" do
   let(:build) { FactoryGirl.create :ci_build, commit: commit }
 
   before do
-    skip_ci_admin_auth
-    login_as :user
+    login_as :admin
   end
 
   describe "GET /admin/builds" do
     before do
       build
-      visit ci_admin_builds_path
+      visit admin_builds_path
     end
 
-    it { expect(page).to have_content "All builds" }
+    it { expect(page).to have_content "Running" }
     it { expect(page).to have_content build.short_sha }
   end
 
@@ -26,43 +25,43 @@ describe "Admin Builds" do
       FactoryGirl.create :ci_build, commit: commit, status: "success"
       FactoryGirl.create :ci_build, commit: commit, status: "failed"
 
-      visit ci_admin_builds_path
+      visit admin_builds_path
+
+      within ".center-top-menu" do
+        click_on "All"
+      end
 
       expect(page.all(".build-link").size).to eq(4)
     end
 
-    it "shows pending builds" do
+    it "shows finished builds" do
       build = FactoryGirl.create :ci_build, commit: commit, status: "pending"
       build1 = FactoryGirl.create :ci_build, commit: commit, status: "running"
       build2 = FactoryGirl.create :ci_build, commit: commit, status: "success"
-      build3 = FactoryGirl.create :ci_build, commit: commit, status: "failed"
 
-      visit ci_admin_builds_path
+      visit admin_builds_path
 
-      within ".nav.nav-tabs" do
-        click_on "Pending"
+      within ".center-top-menu" do
+        click_on "Finished"
       end
 
-      expect(page.find(".build-link")).to have_content(build.id)
+      expect(page.find(".build-link")).not_to have_content(build.id)
       expect(page.find(".build-link")).not_to have_content(build1.id)
-      expect(page.find(".build-link")).not_to have_content(build2.id)
-      expect(page.find(".build-link")).not_to have_content(build3.id)
+      expect(page.find(".build-link")).to have_content(build2.id)
     end
 
     it "shows running builds" do
       build = FactoryGirl.create :ci_build, commit: commit, status: "pending"
-      build1 = FactoryGirl.create :ci_build, commit: commit, status: "running"
       build2 = FactoryGirl.create :ci_build, commit: commit, status: "success"
       build3 = FactoryGirl.create :ci_build, commit: commit, status: "failed"
 
-      visit ci_admin_builds_path
+      visit admin_builds_path
 
-      within ".nav.nav-tabs" do
+      within ".center-top-menu" do
         click_on "Running"
       end
 
-      expect(page.find(".build-link")).to have_content(build1.id)
-      expect(page.find(".build-link")).not_to have_content(build.id)
+      expect(page.find(".build-link")).to have_content(build.id)
       expect(page.find(".build-link")).not_to have_content(build2.id)
       expect(page.find(".build-link")).not_to have_content(build3.id)
     end
diff --git a/spec/features/ci/admin/runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
similarity index 65%
rename from spec/features/ci/admin/runners_spec.rb
rename to spec/features/admin/admin_runners_spec.rb
index b83744f53a8c4ea731a057f4563d47ec57979726..26d03944b8a24dd811e1a6da96f05b73118ae215 100644
--- a/spec/features/ci/admin/runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -10,7 +10,7 @@ describe "Admin Runners" do
       runner = FactoryGirl.create(:ci_runner)
       commit = FactoryGirl.create(:ci_commit)
       FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id)
-      visit ci_admin_runners_path
+      visit admin_runners_path
     end
 
     it { page.has_text? "Manage Runners" }
@@ -36,9 +36,9 @@ describe "Admin Runners" do
     let(:runner) { FactoryGirl.create :ci_runner }
 
     before do
-      @project1 = FactoryGirl.create(:ci_project)
-      @project2 = FactoryGirl.create(:ci_project)
-      visit ci_admin_runner_path(runner)
+      @project1 = FactoryGirl.create(:empty_project)
+      @project2 = FactoryGirl.create(:empty_project)
+      visit admin_runner_path(runner)
     end
 
     describe 'runner info' do
@@ -53,7 +53,7 @@ describe "Admin Runners" do
     describe 'search' do
       before do
         search_form = find('#runner-projects-search')
-        search_form.fill_in 'search', with: @project1.gl_project.name
+        search_form.fill_in 'search', with: @project1.name
         search_form.click_button 'Search'
       end
 
@@ -61,4 +61,26 @@ describe "Admin Runners" do
       it { expect(page).not_to have_content(@project2.name_with_namespace) }
     end
   end
+
+  describe 'runners registration token' do
+    let!(:token) { current_application_settings.runners_registration_token }
+    before { visit admin_runners_path }
+
+    it 'has a registration token' do
+      expect(page).to have_content("Registration token is #{token}")
+      expect(page).to have_selector('#runners-token', text: token)
+    end
+
+    describe 'reload registration token' do
+      let(:page_token) { find('#runners-token').text }
+
+      before do
+        click_button 'Reset runners registration token'
+      end
+
+      it 'changes registration token' do
+        expect(page_token).to_not eq token
+      end
+    end
+  end
 end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 4c756a8e73230191cfdd89f14ebb710383d5aef0..4570e4091284cea19d32166a21f45192814babef 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -87,13 +87,16 @@ describe "Admin::Users", feature: true  do
     end
 
     it "should call send mail" do
-      expect(Notify).to receive(:new_user_email)
+      expect_any_instance_of(NotificationService).to receive(:new_user)
 
       click_button "Create user"
     end
 
     it "should send valid email to user with email & password" do
-      click_button "Create user"
+      perform_enqueued_jobs do
+        click_button "Create user"
+      end
+
       user = User.find_by(username: 'bang')
       email = ActionMailer::Base.deliveries.last
       expect(email.subject).to have_content('Account was created')
@@ -125,6 +128,16 @@ describe "Admin::Users", feature: true  do
 
           expect(page).not_to have_content('Impersonate')
         end
+
+        it 'should not show impersonate button for blocked user' do
+          another_user.block
+
+          visit admin_user_path(another_user)
+
+          expect(page).not_to have_content('Impersonate')
+
+          another_user.activate
+        end
       end
 
       context 'when impersonating' do
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
index 5213ce1099fd0521bfffeac30ce81050aea5c985..f0031a0a247a10f8faa045f02c610e95a6984e9a 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -7,19 +7,19 @@ describe "Builds" do
     login_as(:user)
     @commit = FactoryGirl.create :ci_commit
     @build = FactoryGirl.create :ci_build, commit: @commit
-    @gl_project = @commit.project.gl_project
-    @gl_project.team << [@user, :master]
+    @project = @commit.project
+    @project.team << [@user, :master]
   end
 
   describe "GET /:project/builds" do
     context "Running scope" do
       before do
         @build.run!
-        visit namespace_project_builds_path(@gl_project.namespace, @gl_project)
+        visit namespace_project_builds_path(@project.namespace, @project)
       end
 
       it { expect(page).to have_content 'Running' }
-      it { expect(page).to have_content 'Cancel all' }
+      it { expect(page).to have_content 'Cancel running' }
       it { expect(page).to have_content @build.short_sha }
       it { expect(page).to have_content @build.ref }
       it { expect(page).to have_content @build.name }
@@ -28,41 +28,41 @@ describe "Builds" do
     context "Finished scope" do
       before do
         @build.run!
-        visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :finished)
+        visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
       end
 
       it { expect(page).to have_content 'No builds to show' }
-      it { expect(page).to have_content 'Cancel all' }
+      it { expect(page).to have_content 'Cancel running' }
     end
 
     context "All builds" do
       before do
-        @gl_project.ci_builds.running_or_pending.each(&:success)
-        visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :all)
+        @project.builds.running_or_pending.each(&:success)
+        visit namespace_project_builds_path(@project.namespace, @project, scope: :all)
       end
 
       it { expect(page).to have_content 'All' }
       it { expect(page).to have_content @build.short_sha }
       it { expect(page).to have_content @build.ref }
       it { expect(page).to have_content @build.name }
-      it { expect(page).to_not have_content 'Cancel all' }
+      it { expect(page).to_not have_content 'Cancel running' }
     end
   end
 
   describe "POST /:project/builds/:id/cancel_all" do
     before do
       @build.run!
-      visit namespace_project_builds_path(@gl_project.namespace, @gl_project)
-      click_link "Cancel all"
+      visit namespace_project_builds_path(@project.namespace, @project)
+      click_link "Cancel running"
     end
 
     it { expect(page).to have_content 'No builds to show' }
-    it { expect(page).to_not have_content 'Cancel all' }
+    it { expect(page).to_not have_content 'Cancel running' }
   end
 
   describe "GET /:project/builds/:id" do
     before do
-      visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build)
+      visit namespace_project_build_path(@project.namespace, @project, @build)
     end
 
     it { expect(page).to have_content @commit.sha[0..7] }
@@ -72,7 +72,7 @@ describe "Builds" do
     context "Download artifacts" do
       before do
         @build.update_attributes(artifacts_file: artifacts_file)
-        visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build)
+        visit namespace_project_build_path(@project.namespace, @project, @build)
       end
 
       it { expect(page).to have_content 'Download artifacts' }
@@ -82,7 +82,7 @@ describe "Builds" do
   describe "POST /:project/builds/:id/cancel" do
     before do
       @build.run!
-      visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build)
+      visit namespace_project_build_path(@project.namespace, @project, @build)
       click_link "Cancel"
     end
 
@@ -93,7 +93,7 @@ describe "Builds" do
   describe "POST /:project/builds/:id/retry" do
     before do
       @build.run!
-      visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build)
+      visit namespace_project_build_path(@project.namespace, @project, @build)
       click_link "Cancel"
       click_link 'Retry'
     end
@@ -105,7 +105,7 @@ describe "Builds" do
   describe "GET /:project/builds/:id/download" do
     before do
       @build.update_attributes(artifacts_file: artifacts_file)
-      visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build)
+      visit namespace_project_build_path(@project.namespace, @project, @build)
       click_link 'Download artifacts'
     end
 
diff --git a/spec/features/ci/admin/events_spec.rb b/spec/features/ci/admin/events_spec.rb
deleted file mode 100644
index a7e75cc4f6b116eb5c8fc2477f1cf8a421051fb5..0000000000000000000000000000000000000000
--- a/spec/features/ci/admin/events_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-describe "Admin Events" do
-  let(:event) { FactoryGirl.create :ci_admin_event }
-  
-  before do
-    skip_ci_admin_auth
-    login_as :user
-  end
-
-  describe "GET /admin/events" do
-    before do
-      event
-      visit ci_admin_events_path
-    end
-
-    it { expect(page).to have_content "Events" }
-    it { expect(page).to have_content event.description }
-  end
-end
diff --git a/spec/features/ci/admin/projects_spec.rb b/spec/features/ci/admin/projects_spec.rb
deleted file mode 100644
index b88f55a680700448e64fab3b9351ba6db6edbdb2..0000000000000000000000000000000000000000
--- a/spec/features/ci/admin/projects_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-describe "Admin Projects" do
-  let(:project) { FactoryGirl.create :ci_project }
-
-  before do
-    skip_ci_admin_auth
-    login_as :user
-  end
-
-  describe "GET /admin/projects" do
-    before do
-      project
-      visit ci_admin_projects_path
-    end
-
-    it { expect(page).to have_content "Projects" }
-  end
-end
diff --git a/spec/features/ci/lint_spec.rb b/spec/features/ci/lint_spec.rb
deleted file mode 100644
index 5d8f56e2cfb160bac8f81f0e6a41e276d0a7713a..0000000000000000000000000000000000000000
--- a/spec/features/ci/lint_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-describe "Lint" do
-  before do
-    login_as :user
-  end
-
-  it "Yaml parsing", js: true do
-    content = File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
-    visit ci_lint_path 
-    fill_in "content", with: content
-    click_on "Validate"
-    within "table" do
-      expect(page).to have_content("Job - rspec")
-      expect(page).to have_content("Job - spinach")
-      expect(page).to have_content("Deploy Job - staging")
-      expect(page).to have_content("Deploy Job - production")
-    end
-  end
-
-  it "Yaml parsing with error", js: true do
-    visit ci_lint_path
-    fill_in "content", with: ""
-    click_on "Validate"
-    expect(page).to have_content("Status: syntax is incorrect")
-    expect(page).to have_content("Error: Please provide content of .gitlab-ci.yml")
-  end
-end
diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e6e73e5e67ca34dd3ea2734fc30842bdf0d9c6a2
--- /dev/null
+++ b/spec/features/ci_lint_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe 'CI Lint' do
+  before do
+    login_as :user
+  end
+
+  describe 'YAML parsing' do
+    before do
+      visit ci_lint_path
+      fill_in 'content', with: yaml_content
+      click_on 'Validate'
+    end
+
+    context 'YAML is correct' do
+      let(:yaml_content) do
+        File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+      end
+
+      it 'Yaml parsing' do
+        within "table" do
+          expect(page).to have_content('Job - rspec')
+          expect(page).to have_content('Job - spinach')
+          expect(page).to have_content('Deploy Job - staging')
+          expect(page).to have_content('Deploy Job - production')
+        end
+      end
+    end
+
+    context 'YAML is incorrect' do
+      let(:yaml_content) { '' }
+
+      it 'displays information about an error' do
+        expect(page).to have_content('Status: syntax is incorrect')
+        expect(page).to have_content('Error: Please provide content of .gitlab-ci.yml')
+      end
+    end
+  end
+end
diff --git a/spec/features/ci_settings_spec.rb b/spec/features/ci_settings_spec.rb
deleted file mode 100644
index 7e25e8830182b10ca7f3ca797688de8eecd7f896..0000000000000000000000000000000000000000
--- a/spec/features/ci_settings_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'spec_helper'
-
-describe "CI settings" do
-  let(:user) { create(:user) }
-  before { login_as(user) }
-
-  before do
-    @project = FactoryGirl.create :ci_project
-    @gl_project = @project.gl_project
-    @gl_project.team << [user, :master]
-    visit edit_namespace_project_ci_settings_path(@gl_project.namespace, @gl_project)
-  end
-
-  it { expect(page).to have_content 'Build Schedule' }
-
-  it "updates configuration" do
-    fill_in 'Timeout', with: '70'
-    click_button 'Save changes'
-    expect(page).to have_content 'was successfully updated'
-    expect(find_field('Timeout').value).to eq '70'
-  end
-end
diff --git a/spec/features/ci_web_hooks_spec.rb b/spec/features/ci_web_hooks_spec.rb
deleted file mode 100644
index efae0a42c1ef00da94ba694a4a18c0f2de894b30..0000000000000000000000000000000000000000
--- a/spec/features/ci_web_hooks_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'spec_helper'
-
-describe 'CI web hooks' do
-  let(:user) { create(:user) }
-  before { login_as(user) }
-
-  before do
-    @project = FactoryGirl.create :ci_project
-    @gl_project = @project.gl_project
-    @gl_project.team << [user, :master]
-    visit namespace_project_ci_web_hooks_path(@gl_project.namespace, @gl_project)
-  end
-
-  context 'create a trigger' do
-    before do
-      fill_in 'web_hook_url', with: 'http://example.com'
-      click_on 'Add Web Hook'
-    end
-
-    it { expect(@project.web_hooks.count).to eq(1) }
-
-    it 'revokes the trigger' do
-      click_on 'Remove'
-      expect(@project.web_hooks.count).to eq(0)
-    end
-  end
-end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 90739cd6a28f1fdece82ca309f1d1c429e8db2e9..fe7f07f5b75017293b4772d71c7ecd746a2da46c 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -1,74 +1,99 @@
 require 'spec_helper'
 
-describe "Commits" do
+describe 'Commits' do
   include CiStatusHelper
 
   let(:project) { create(:project) }
 
-  describe "CI" do
+  describe 'CI' do
     before do
       login_as :user
       project.team << [@user, :master]
-      @ci_project = project.ensure_gitlab_ci_project
-      @commit = FactoryGirl.create :ci_commit, gl_project: project, sha: project.commit.sha
-      @build = FactoryGirl.create :ci_build, commit: @commit
-      @generic_status = FactoryGirl.create :generic_commit_status, commit: @commit
+      stub_ci_commit_to_return_yaml_file
     end
 
-    before do
-      stub_ci_commit_to_return_yaml_file
+    let!(:commit) do
+      FactoryGirl.create :ci_commit, project: project, sha: project.commit.sha
+    end
+
+    let!(:build) { FactoryGirl.create :ci_build, commit: commit }
+
+    describe 'Project commits' do
+      before do
+        visit namespace_project_commits_path(project.namespace, project, :master)
+      end
+
+      it 'should show build status' do
+        page.within("//li[@id='commit-#{commit.short_sha}']") do
+          expect(page).to have_css(".ci-status-link")
+        end
+      end
     end
 
-    describe "GET /:project/commits/:sha/ci" do
+    describe 'Commit builds' do
       before do
-        visit ci_status_path(@commit)
+        visit ci_status_path(commit)
       end
 
-      it { expect(page).to have_content @commit.sha[0..7] }
-      it { expect(page).to have_content @commit.git_commit_message }
-      it { expect(page).to have_content @commit.git_author_name }
+      it { expect(page).to have_content commit.sha[0..7] }
+      it { expect(page).to have_content commit.git_commit_message }
+      it { expect(page).to have_content commit.git_author_name }
     end
 
-    context "Download artifacts" do
+    context 'Download artifacts' do
       let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
 
       before do
-        @build.update_attributes(artifacts_file: artifacts_file)
+        build.update_attributes(artifacts_file: artifacts_file)
       end
 
       it do
-        visit ci_status_path(@commit)
-        click_on "Download artifacts"
+        visit ci_status_path(commit)
+        click_on 'Download artifacts'
         expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
       end
     end
 
-    describe "Cancel all builds" do
-      it "cancels commit" do
-        visit ci_status_path(@commit)
-        click_on "Cancel running"
-        expect(page).to have_content "canceled"
+    describe 'Cancel all builds' do
+      it 'cancels commit' do
+        visit ci_status_path(commit)
+        click_on 'Cancel running'
+        expect(page).to have_content 'canceled'
       end
     end
 
-    describe "Cancel build" do
-      it "cancels build" do
-        visit ci_status_path(@commit)
-        click_on "Cancel"
-        expect(page).to have_content "canceled"
+    describe 'Cancel build' do
+      it 'cancels build' do
+        visit ci_status_path(commit)
+        click_on 'Cancel'
+        expect(page).to have_content 'canceled'
       end
     end
 
-    describe ".gitlab-ci.yml not found warning" do
-      it "does not show warning" do
-        visit ci_status_path(@commit)
-        expect(page).not_to have_content ".gitlab-ci.yml not found in this commit"
+    describe '.gitlab-ci.yml not found warning' do
+      context 'ci builds enabled' do
+        it "does not show warning" do
+          visit ci_status_path(commit)
+          expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+        end
+
+        it 'shows warning' do
+          stub_ci_commit_yaml_file(nil)
+          visit ci_status_path(commit)
+          expect(page).to have_content '.gitlab-ci.yml not found in this commit'
+        end
       end
 
-      it "shows warning" do
-        stub_ci_commit_yaml_file(nil)
-        visit ci_status_path(@commit)
-        expect(page).to have_content ".gitlab-ci.yml not found in this commit"
+      context 'ci builds disabled' do
+        before do
+          stub_ci_builds_disabled
+          stub_ci_commit_yaml_file(nil)
+          visit ci_status_path(commit)
+        end
+
+        it 'does not show warning' do
+          expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+        end
       end
     end
   end
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
index f600f8684acfcf2eeec6a5e73bc1c964fb2b4498..38c8d343ce3bd79b1462905104e464fbebec152f 100644
--- a/spec/features/issues/filter_by_milestone_spec.rb
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -13,7 +13,7 @@ feature 'Issue filtering by Milestone', feature: true do
     visit_issues(project)
     filter_by_milestone(Milestone::None.title)
 
-    expect(page).to have_css('.issue-title', count: 1)
+    expect(page).to have_css('.title', count: 1)
   end
 
   scenario 'filters by a specific Milestone', js: true do
@@ -23,7 +23,7 @@ feature 'Issue filtering by Milestone', feature: true do
     visit_issues(project)
     filter_by_milestone(milestone.title)
 
-    expect(page).to have_css('.issue-title', count: 1)
+    expect(page).to have_css('.title', count: 1)
   end
 
   def visit_issues(project)
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e4efdbe2421d0dfc5838dcba1bac30cb705c4e2b
--- /dev/null
+++ b/spec/features/issues/note_polling_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+feature 'Issue notes polling' do
+  let!(:project) { create(:project, :public) }
+  let!(:issue) { create(:issue, project: project) }
+
+  background do
+    visit namespace_project_issue_path(project.namespace, project, issue)
+  end
+
+  scenario 'Another user adds a comment to an issue', js: true do
+    note = create(:note_on_issue, noteable: issue, note: 'Looks good!')
+    page.execute_script('notes.refresh();')
+    expect(page).to have_selector("#note_#{note.id}", text: 'Looks good!')
+  end
+end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 32fd4065bb41d55ca0c25f8dcaaf5d573dd897d8..a2fb3e4c75dd1a34ed3108f0d5671dae668768f3 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -61,7 +61,7 @@ describe 'Issues', feature: true do
     it 'allows user to select unasigned', js: true do
       visit edit_namespace_project_issue_path(project.namespace, project, issue)
 
-      expect(page).to have_content "Assign to #{@user.name}"
+      expect(page).to have_content "Assignee #{@user.name}"
 
       first('#s2id_issue_assignee_id').click
       sleep 2 # wait for ajax stuff to complete
@@ -69,7 +69,10 @@ describe 'Issues', feature: true do
 
       click_button 'Save changes'
 
-      expect(page).to have_content 'Assignee: none'
+      page.within('.assignee') do
+        expect(page).to have_content 'None'
+      end
+
       expect(issue.reload.assignee).to be_nil
     end
   end
@@ -202,11 +205,11 @@ describe 'Issues', feature: true do
       it 'with dropdown menu' do
         visit namespace_project_issue_path(project.namespace, project, issue)
 
-        find('.context #issue_assignee_id').
+        find('.issuable-sidebar #issue_assignee_id').
           set project.team.members.first.id
         click_button 'Update Issue'
 
-        expect(page).to have_content 'Assignee:'
+        expect(page).to have_content 'Assignee'
         has_select?('issue_assignee_id',
                     selected: project.team.members.first.name)
       end
@@ -241,12 +244,16 @@ describe 'Issues', feature: true do
       it 'with dropdown menu' do
         visit namespace_project_issue_path(project.namespace, project, issue)
 
-        find('.context').
+        find('.issuable-sidebar').
           select(milestone.title, from: 'issue_milestone_id')
         click_button 'Update Issue'
 
         expect(page).to have_content "Milestone changed to #{milestone.title}"
-        expect(page).to have_content "Milestone: #{milestone.title}"
+
+        page.within('.milestone') do
+          expect(page).to have_content milestone.title
+        end
+
         has_select?('issue_assignee_id', selected: milestone.title)
       end
     end
@@ -279,13 +286,19 @@ describe 'Issues', feature: true do
 
       it 'allows user to remove assignee', js: true do
         visit namespace_project_issue_path(project.namespace, project, issue)
-        expect(page).to have_content "Assignee: #{user2.name}"
 
-        first('#s2id_issue_assignee_id').click
+        page.within('.assignee') do
+          expect(page).to have_content user2.name
+        end
+
+        find('.assignee .edit-link').click
         sleep 2 # wait for ajax stuff to complete
         first('.user-result').click
 
-        expect(page).to have_content 'Assignee: none'
+        page.within('.assignee') do
+          expect(page).to have_content 'None'
+        end
+
         sleep 2 # wait for ajax stuff to complete
         expect(issue.reload.assignee).to be_nil
       end
@@ -293,10 +306,10 @@ describe 'Issues', feature: true do
   end
 
   def first_issue
-    page.all('ul.issues-list li').first.text
+    page.all('ul.issues-list > li').first.text
   end
 
   def last_issue
-    page.all('ul.issues-list li').last.text
+    page.all('ul.issues-list > li').last.text
   end
 end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 922c76285d1865baca9aa06007aab96e9985a7df..2451e56fe7ce6338b3863e06dee98c2351bec2bc 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -98,4 +98,56 @@ feature 'Login', feature: true do
       expect(page).to have_content('Invalid login or password.')
     end
   end
+
+  describe 'with required two-factor authentication enabled' do
+    let(:user) { create(:user) }
+    before(:each) { stub_application_setting(require_two_factor_authentication: true) }
+
+    context 'with grace period defined' do
+      before(:each) do
+        stub_application_setting(two_factor_grace_period: 48)
+        login_with(user)
+      end
+
+      context 'within the grace period' do
+        it 'redirects to two-factor configuration page' do
+          expect(current_path).to eq new_profile_two_factor_auth_path
+          expect(page).to have_content('You must configure Two-Factor Authentication in your account until')
+        end
+
+        it 'two-factor configuration is skippable' do
+          expect(current_path).to eq new_profile_two_factor_auth_path
+
+          click_link 'Configure it later'
+          expect(current_path).to eq root_path
+        end
+      end
+
+      context 'after the grace period' do
+        let(:user) { create(:user, otp_grace_period_started_at: 9999.hours.ago) }
+
+        it 'redirects to two-factor configuration page' do
+          expect(current_path).to eq new_profile_two_factor_auth_path
+          expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
+        end
+
+        it 'two-factor configuration is not skippable' do
+          expect(current_path).to eq new_profile_two_factor_auth_path
+          expect(page).not_to have_link('Configure it later')
+        end
+      end
+    end
+
+    context 'without grace pariod defined' do
+      before(:each) do
+        stub_application_setting(two_factor_grace_period: 0)
+        login_with(user)
+      end
+
+      it 'redirects to two-factor configuration page' do
+        expect(current_path).to eq new_profile_two_factor_auth_path
+        expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
+      end
+    end
+  end
 end
diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7aa7eb965e908ee173aeda4fff7b815414389a2d
--- /dev/null
+++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+feature 'Merge When Build Succeeds', feature: true, js: true do
+  let(:user) { create(:user) }
+
+  let(:project)       { create(:project, :public) }
+  let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") }
+
+  before do
+    project.team << [user, :master]
+    project.enable_ci
+  end
+
+  context "Active build for Merge Request" do
+    let!(:ci_commit) { create(:ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
+    let!(:ci_build) { create(:ci_build, commit: ci_commit) }
+
+    before do
+      login_as user
+      visit_merge_request(merge_request)
+    end
+
+    it 'displays the Merge When Build Succeeds button' do
+      expect(page).to have_button "Merge When Build Succeeds"
+    end
+
+    context "Merge When Build succeeds enabled" do
+      before do
+        click_button "Merge When Build Succeeds"
+      end
+
+      it 'activates Merge When Build Succeeds feature' do
+        expect(page).to have_link "Cancel Automatic Merge"
+
+        expect(page).to have_content "Set by #{user.name} to be merged automatically when the build succeeds."
+        expect(page).to have_content "The source branch will not be removed."
+
+        visit_merge_request(merge_request) # Needed to refresh the page
+        expect(page).to have_content /Enabled an automatic merge when the build for [0-9a-f]{8} succeeds/i
+      end
+    end
+  end
+
+  context 'When it is enabled' do
+    let(:merge_request) do
+      create(:merge_request_with_diffs, :simple,  source_project: project, author: user,
+                                                  merge_user: user, title: "MepMep", merge_when_build_succeeds: true)
+    end
+
+    let!(:ci_commit) { create(:ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
+    let!(:ci_build) { create(:ci_build, commit: ci_commit) }
+
+    before do
+      login_as user
+      visit_merge_request(merge_request)
+    end
+
+    it 'cancels the automatic merge' do
+      click_link "Cancel Automatic Merge"
+
+      expect(page).to have_button "Merge When Build Succeeds"
+
+      visit_merge_request(merge_request) # Needed to refresh the page
+      expect(page).to have_content "Canceled the automatic merge"
+    end
+
+    it "allows the user to remove the source branch" do
+      expect(page).to have_link "Remove Source Branch When Merged"
+
+      click_link "Remove Source Branch When Merged"
+      expect(page).to have_content "The source branch will be removed"
+    end
+  end
+
+  context 'Build is not active' do
+    it "should not allow for enabling" do
+      visit_merge_request(merge_request)
+      expect(page).not_to have_link "Merge When Build Succeeds"
+    end
+  end
+
+  def visit_merge_request(merge_request)
+    visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+  end
+end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index d7cb3b2e86ead6487a8e965f03168475274c9c96..f0fc6916c4d2cdb6039a06ae98ca4f958b9c1e61 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
 
 describe 'Comments', feature: true do
   include RepoHelpers
+  include WaitForAjax
 
   describe 'On a merge request', js: true, feature: true do
     let!(:merge_request) { create(:merge_request) }
@@ -123,8 +124,8 @@ describe 'Comments', feature: true do
         it 'removes the attachment div and resets the edit form' do
           find('.js-note-attachment-delete').click
           is_expected.not_to have_css('.note-attachment')
-          expect(find('.current-note-edit-form', visible: false)).
-            not_to be_visible
+          is_expected.not_to have_css('.current-note-edit-form')
+          wait_for_ajax
         end
       end
     end
diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb
index 85e70b4d47f3dcbf547724b6b0991c3e4447ac6b..257d363438cdf9ea4ee42f9f13be88fa2d739146 100644
--- a/spec/features/password_reset_spec.rb
+++ b/spec/features/password_reset_spec.rb
@@ -3,11 +3,12 @@ require 'spec_helper'
 feature 'Password reset', feature: true do
   describe 'throttling' do
     it 'sends reset instructions when not previously sent' do
-      visit root_path
-      forgot_password(create(:user))
+      user = create(:user)
+      forgot_password(user)
 
-      expect(page).to have_content(I18n.t('devise.passwords.send_instructions'))
+      expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions'))
       expect(current_path).to eq new_user_session_path
+      expect(user.recently_sent_password_reset?).to be_truthy
     end
 
     it 'sends reset instructions when previously sent more than a minute ago' do
@@ -15,26 +16,25 @@ feature 'Password reset', feature: true do
       user.send_reset_password_instructions
       user.update_attribute(:reset_password_sent_at, 5.minutes.ago)
 
-      visit root_path
-      forgot_password(user)
-
-      expect(page).to have_content(I18n.t('devise.passwords.send_instructions'))
+      expect{ forgot_password(user) }.to change{ user.reset_password_sent_at }
+      expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions'))
       expect(current_path).to eq new_user_session_path
     end
 
-    it "throttles multiple resets in a short timespan" do
+    it 'throttles multiple resets in a short timespan' do
       user = create(:user)
       user.send_reset_password_instructions
+      # Reload because PG handles datetime less precisely than Ruby/Rails
+      user.reload
 
-      visit root_path
-      forgot_password(user)
-
-      expect(page).to have_content(I18n.t('devise.passwords.recently_reset'))
-      expect(current_path).to eq new_user_password_path
+      expect{ forgot_password(user) }.not_to change{ user.reset_password_sent_at }
+      expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions'))
+      expect(current_path).to eq new_user_session_path
     end
   end
 
   def forgot_password(user)
+    visit root_path
     click_on 'Forgot your password?'
     fill_in 'Email', with: user.email
     click_button 'Reset password'
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 09fcff2444ac83771442c68f0935fd591fdd4305..74b148f5d178b44fb55aa3340cbca7f4fefc5180 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -70,6 +70,20 @@ feature 'Project', feature: true do
     end
   end
 
+  describe 'leave project link' do
+    let(:user)    { create(:user) }
+    let(:project) { create(:project, namespace: user.namespace) }
+
+    before do
+      login_with(user)
+      project.team.add_user(user, Gitlab::Access::MASTER)
+      visit namespace_project_path(project.namespace, project)
+    end
+
+    it { expect(page).to have_content('You have Master access to this project.') }
+    it { expect(page).to have_link('Leave this project') }
+  end
+
   def remove_with_confirm(button_text, confirm_with)
     click_button button_text
     fill_in 'confirm_name_input', with: confirm_with
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index b025902663068080afd7cc28ed6a320ca979c96f..d97831aae14c62abb5097aff982e12e50a61a54c 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -8,14 +8,14 @@ describe "Runners" do
 
   describe "specific runners" do
     before do
-      @project = FactoryGirl.create :ci_project
-      @project.gl_project.team << [user, :master]
+      @project = FactoryGirl.create :empty_project, shared_runners_enabled: false
+      @project.team << [user, :master]
 
-      @project2 = FactoryGirl.create :ci_project
-      @project2.gl_project.team << [user, :master]
+      @project2 = FactoryGirl.create :empty_project
+      @project2.team << [user, :master]
 
-      @project3 = FactoryGirl.create :ci_project
-      @project3.gl_project.team << [user, :developer]
+      @project3 = FactoryGirl.create :empty_project
+      @project3.team << [user, :developer]
 
       @shared_runner = FactoryGirl.create :ci_shared_runner
       @specific_runner = FactoryGirl.create :ci_specific_runner
@@ -25,7 +25,7 @@ describe "Runners" do
       @project2.runners << @specific_runner2
       @project3.runners << @specific_runner3
 
-      visit runners_path(@project.gl_project)
+      visit runners_path(@project)
     end
 
     before do
@@ -49,7 +49,7 @@ describe "Runners" do
 
     it "disables specific runner for project" do
       @project2.runners << @specific_runner
-      visit runners_path(@project.gl_project)
+      visit runners_path(@project)
 
       within ".activated-specific-runners" do
         click_on "Disable for this project"
@@ -69,9 +69,9 @@ describe "Runners" do
 
   describe "shared runners" do
     before do
-      @project = FactoryGirl.create :ci_project
-      @project.gl_project.team << [user, :master]
-      visit runners_path(@project.gl_project)
+      @project = FactoryGirl.create :empty_project, shared_runners_enabled: false
+      @project.team << [user, :master]
+      visit runners_path(@project)
     end
 
     it "enables shared runners" do
@@ -82,14 +82,14 @@ describe "Runners" do
 
   describe "show page" do
     before do
-      @project = FactoryGirl.create :ci_project
-      @project.gl_project.team << [user, :master]
+      @project = FactoryGirl.create :empty_project
+      @project.team << [user, :master]
       @specific_runner = FactoryGirl.create :ci_specific_runner
       @project.runners << @specific_runner
     end
 
     it "shows runner information" do
-      visit runners_path(@project.gl_project)
+      visit runners_path(@project)
       click_on @specific_runner.short_sha
       expect(page).to have_content(@specific_runner.platform)
     end
diff --git a/spec/features/security/group_access_spec.rb b/spec/features/security/group_access_spec.rb
index 4b78e3a61f04ebeea7ffd9953bcccf7da94b6bfd..65f8073c6933937f2264d17103af85671ab64951 100644
--- a/spec/features/security/group_access_spec.rb
+++ b/spec/features/security/group_access_spec.rb
@@ -16,11 +16,11 @@ describe 'Group access', feature: true do
     end
   end
 
-  def group_member(access_level, group = group)
+  def group_member(access_level, grp = group())
     level = Object.const_get("Gitlab::Access::#{access_level.upcase}")
 
     create(:user).tap do |user|
-      group.add_user(user, level)
+      grp.add_user(user, level)
     end
   end
 
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index fca3c77fc64464e9e8a1fda7d31c237b7c699902..b7368cca29d0cdb3dbfcca4f7910718866e10cc7 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -47,7 +47,7 @@ feature 'Task Lists', feature: true do
     it 'contains the required selectors' do
       visit_issue(project, issue)
 
-      container = '.issue-details .description.js-task-list-container'
+      container = '.detail-page-description .description.js-task-list-container'
 
       expect(page).to have_selector(container)
       expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
@@ -123,7 +123,7 @@ feature 'Task Lists', feature: true do
     it 'contains the required selectors' do
       visit_merge_request(project, merge)
 
-      container = '.merge-request-details .description.js-task-list-container'
+      container = '.detail-page-description .description.js-task-list-container'
 
       expect(page).to have_selector(container)
       expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 69492d58878f57bf4409a7180b8efe88828654bc..3cbc8253ad6260d52a4c10d44789b836dfb93f77 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -5,10 +5,9 @@ describe 'Triggers' do
   before { login_as(user) }
 
   before do
-    @project = FactoryGirl.create :ci_project
-    @gl_project = @project.gl_project
-    @gl_project.team << [user, :master]
-    visit namespace_project_triggers_path(@gl_project.namespace, @gl_project)
+    @project = FactoryGirl.create :empty_project
+    @project.team << [user, :master]
+    visit namespace_project_triggers_path(@project.namespace, @project)
   end
 
   context 'create a trigger' do
diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb
index adb602f3edd211e8862033ab9b2529fb64a07722..afea1840cd742d0e9673743521e0ab759fc59946 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -6,13 +6,12 @@ describe "Variables" do
 
   describe "specific runners" do
     before do
-      @project = FactoryGirl.create :ci_project
-      @gl_project = @project.gl_project
-      @gl_project.team << [user, :master]
+      @project = FactoryGirl.create :empty_project
+      @project.team << [user, :master]
     end
 
     it "creates variable", js: true do
-      visit namespace_project_variables_path(@gl_project.namespace, @gl_project)
+      visit namespace_project_variables_path(@project.namespace, @project)
       click_on "Add a variable"
       fill_in "Key", with: "SECRET_KEY"
       fill_in "Value", with: "SECRET_VALUE"
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 41d12afa9ce8f41561c7f960f9e87f567bec0a06..e8dfc5c0eb173c5a8677ba7def2e8c09612338c0 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -153,6 +153,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Ignores invalid: <%= User.reference_prefix %>fake_user
 - Ignored in code: `<%= user.to_reference %>`
 - Ignored in links: [Link to <%= user.to_reference %>](#user-link)
+- Link to user by reference: [User](<%= user.to_reference %>)
 
 #### IssueReferenceFilter
 
@@ -160,6 +161,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Issue in another project: <%= xissue.to_reference(project) %>
 - Ignored in code: `<%= issue.to_reference %>`
 - Ignored in links: [Link to <%= issue.to_reference %>](#issue-link)
+- Issue by URL: <%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>
+- Link to issue by reference: [Issue](<%= issue.to_reference %>)
+- Link to issue by URL: [Issue](<%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>)
 
 #### MergeRequestReferenceFilter
 
@@ -167,6 +171,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Merge request in another project: <%= xmerge_request.to_reference(project) %>
 - Ignored in code: `<%= merge_request.to_reference %>`
 - Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link)
+- Merge request by URL: <%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>
+- Link to merge request by reference: [Merge request](<%= merge_request.to_reference %>)
+- Link to merge request by URL: [Merge request](<%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>)
 
 #### SnippetReferenceFilter
 
@@ -174,6 +181,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Snippet in another project: <%= xsnippet.to_reference(project) %>
 - Ignored in code: `<%= snippet.to_reference %>`
 - Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link)
+- Snippet by URL: <%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>
+- Link to snippet by reference: [Snippet](<%= snippet.to_reference %>)
+- Link to snippet by URL: [Snippet](<%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>)
 
 #### CommitRangeReferenceFilter
 
@@ -181,6 +191,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Range in another project: <%= xcommit_range.to_reference(project) %>
 - Ignored in code: `<%= commit_range.to_reference %>`
 - Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link)
+- Range by URL: <%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>
+- Link to range by reference: [Range](<%= commit_range.to_reference %>)
+- Link to range by URL: [Range](<%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>)
 
 #### CommitReferenceFilter
 
@@ -188,6 +201,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Commit in another project: <%= xcommit.to_reference(project) %>
 - Ignored in code: `<%= commit.to_reference %>`
 - Ignored in links: [Link to <%= commit.to_reference %>](#commit-link)
+- Commit by URL: <%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>
+- Link to commit by reference: [Commit](<%= commit.to_reference %>)
+- Link to commit by URL: [Commit](<%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>)
 
 #### LabelReferenceFilter
 
@@ -196,6 +212,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
 - Label by name in quotes: <%= label.to_reference(:name) %>
 - Ignored in code: `<%= simple_label.to_reference %>`
 - Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link)
+- Link to label by reference: [Label](<%= label.to_reference %>)
 
 ### Task Lists
 
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 1dfae0fbd3fea83a4f0966f20f27d5cfbeb880f2..68527c3a4f84b173bf8e30e55e81e7d6d1963cd1 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -59,7 +59,7 @@ describe ApplicationHelper do
 
       avatar_url = "http://localhost/uploads/project/avatar/#{project.id}/banana_sample.gif"
       expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).
-        to eq "<img alt=\"Banana sample\" src=\"#{avatar_url}\" />"
+        to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
     end
 
     it 'should give uploaded icon when present' do
@@ -95,9 +95,9 @@ describe ApplicationHelper do
     end
 
     it 'should call gravatar_icon when no User exists with the given email' do
-      expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20)
+      expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2)
 
-      helper.avatar_icon('foo@example.com', 20)
+      helper.avatar_icon('foo@example.com', 20, 2)
     end
 
     describe 'using a User' do
@@ -150,15 +150,19 @@ describe ApplicationHelper do
         stub_gravatar_setting(plain_url: 'http://example.local/?s=%{size}&hash=%{hash}')
 
         expect(gravatar_icon(user_email, 20)).
-          to eq('http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118')
+          to eq('http://example.local/?s=40&hash=b58c6f14d292556214bd64909bcdb118')
       end
 
       it 'accepts a custom size argument' do
-        expect(helper.gravatar_icon(user_email, 64)).to include '?s=64'
+        expect(helper.gravatar_icon(user_email, 64)).to include '?s=128'
       end
 
-      it 'defaults size to 40 when given an invalid size' do
-        expect(helper.gravatar_icon(user_email, nil)).to include '?s=40'
+      it 'defaults size to 40@2x when given an invalid size' do
+        expect(helper.gravatar_icon(user_email, nil)).to include '?s=80'
+      end
+
+      it 'accepts a scaling factor' do
+        expect(helper.gravatar_icon(user_email, 40, 3)).to include '?s=120'
       end
 
       it 'ignores case and surrounding whitespace' do
@@ -259,11 +263,12 @@ describe ApplicationHelper do
     end
 
     it 'includes a default js-timeago class' do
-      expect(element.attr('class')).to eq 'time_ago js-timeago'
+      expect(element.attr('class')).to eq 'time_ago js-timeago js-timeago-pending'
     end
 
     it 'accepts a custom html_class' do
-      expect(element(html_class: 'custom_class').attr('class')).to eq 'custom_class js-timeago'
+      expect(element(html_class: 'custom_class').attr('class')).
+        to eq 'custom_class js-timeago js-timeago-pending'
     end
 
     it 'accepts a custom tooltip placement' do
@@ -274,7 +279,7 @@ describe ApplicationHelper do
       el = element.next_element
 
       expect(el.name).to eq 'script'
-      expect(el.text).to include "$('.js-timeago').timeago()"
+      expect(el.text).to include "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()"
     end
 
     it 'allows the script tag to be excluded' do
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index 7fc53eb1472e321927c93eb7a3ad7a667f2e2df0..4f8d9c672620ce6c64b1c092b7944ca896c1e177 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -6,13 +6,8 @@ describe CiStatusHelper do
   let(:success_commit) { double("Ci::Commit", status: 'success') }
   let(:failed_commit) { double("Ci::Commit", status: 'failed') }
 
-  describe 'ci_status_color' do
-    it { expect(ci_status_icon(success_commit)).to include('fa-check') }
-    it { expect(ci_status_icon(failed_commit)).to include('fa-close') }
-  end
-
-  describe 'ci_status_color' do
-    it { expect(ci_status_color(success_commit)).to eq('green') }
-    it { expect(ci_status_color(failed_commit)).to eq('red') }
+  describe 'ci_status_icon' do
+    it { expect(helper.ci_status_icon(success_commit)).to include('fa-check') }
+    it { expect(helper.ci_status_icon(failed_commit)).to include('fa-close') }
   end
 end
diff --git a/spec/helpers/groups_helper.rb b/spec/helpers/groups_helper.rb
index 5d1744606818a3efbc04e15f22cc444fb49c644d..4ea90a80a926649ba2597bf08ddad0831a8c14e8 100644
--- a/spec/helpers/groups_helper.rb
+++ b/spec/helpers/groups_helper.rb
@@ -9,7 +9,7 @@ describe GroupsHelper do
       group.avatar = File.open(avatar_file_path)
       group.save!
       expect(group_icon(group.path).to_s).
-        to match("/uploads/group/avatar/#{ group.id }/banana_sample.gif")
+        to match("/uploads/group/avatar/#{group.id}/banana_sample.gif")
     end
 
     it 'should give default avatar_icon when no avatar is present' do
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 1f2c4ee77b59fdf79c94f5295f3c68048f5136c3..ffd8ebae029726441a736585a9dd7dae48678fac 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -127,18 +127,6 @@ describe IssuesHelper do
     it { is_expected.to eq("!1, !2, or !3") }
   end
 
-  describe "#url_to_emoji" do
-    it "returns url" do
-      expect(url_to_emoji("smile")).to include("emoji/1F604.png")
-    end
-  end
-
-  describe "#emoji_list" do
-    it "returns url" do
-      expect(emoji_list).to be_kind_of(Array)
-    end
-  end
-
   describe "#note_active_class" do
     before do
       @note = create :note
@@ -153,4 +141,11 @@ describe IssuesHelper do
       expect(note_active_class(Note.all, @note.author)).to eq("active")
     end
   end
+
+  describe "#awards_sort" do
+    it "sorts a hash so thumbsup and thumbsdown are always on top" do
+      data = { "thumbsdown" => "some value", "lifter" => "some value", "thumbsup" => "some value" }
+      expect(awards_sort(data).keys).to eq(["thumbsup", "thumbsdown", "lifter"])
+    end
+  end
 end
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 0ef1efb8bce1ee6c01105aab7be74fd62b8af73a..600e1c4e9ecf1727403f68d7cca18bb270be25d7 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -1,24 +1,57 @@
 require 'spec_helper'
 
 describe MergeRequestsHelper do
-  describe "#issues_sentence" do
+  describe 'ci_build_details_path' do
+    let(:project) { create :project }
+    let(:merge_request) { MergeRequest.new }
+    let(:ci_service) { CiService.new }
+    let(:last_commit) { Ci::Commit.new({}) }
+
+    before do
+      allow(merge_request).to receive(:source_project).and_return(project)
+      allow(merge_request).to receive(:last_commit).and_return(last_commit)
+      allow(project).to receive(:ci_service).and_return(ci_service)
+      allow(last_commit).to receive(:sha).and_return('12d65c')
+    end
+
+    it 'does not include api credentials in a link' do
+      allow(ci_service).
+        to receive(:build_page).and_return("http://secretuser:secretpass@jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c")
+      expect(helper.ci_build_details_path(merge_request)).to_not match("secret")
+    end
+  end
+
+  describe '#issues_sentence' do
     subject { issues_sentence(issues) }
     let(:issues) do
       [build(:issue, iid: 1), build(:issue, iid: 2), build(:issue, iid: 3)]
     end
 
     it { is_expected.to eq('#1, #2, and #3') }
+
+    context 'for JIRA issues' do
+      let(:project) { create(:project) }
+      let(:issues) do
+        [
+          JiraIssue.new('JIRA-123', project),
+          JiraIssue.new('JIRA-456', project),
+          JiraIssue.new('FOOBAR-7890', project)
+        ]
+      end
+
+      it { is_expected.to eq('FOOBAR-7890, JIRA-123, and JIRA-456') }
+    end
   end
 
-  describe "#format_mr_branch_names" do
-    describe "within the same project" do
+  describe '#format_mr_branch_names' do
+    describe 'within the same project' do
       let(:merge_request) { create(:merge_request) }
       subject { format_mr_branch_names(merge_request) }
 
       it { is_expected.to eq([merge_request.source_branch, merge_request.target_branch]) }
     end
 
-    describe "within different projects" do
+    describe 'within different projects' do
       let(:project) { create(:project) }
       let(:fork_project) { create(:project, forked_from_project: project) }
       let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) }
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fd7107779f6cd99b2d5f3349be12573025811062
--- /dev/null
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -0,0 +1,129 @@
+require 'rails_helper'
+
+describe PageLayoutHelper do
+  describe 'page_description' do
+    it 'defaults to value returned by page_description_default helper' do
+      allow(helper).to receive(:page_description_default).and_return('Foo')
+
+      expect(helper.page_description).to eq 'Foo'
+    end
+
+    it 'returns the last-pushed description' do
+      helper.page_description('Foo')
+      helper.page_description('Bar')
+      helper.page_description('Baz')
+
+      expect(helper.page_description).to eq 'Baz'
+    end
+
+    it 'squishes multiple newlines' do
+      helper.page_description("Foo\nBar\nBaz")
+
+      expect(helper.page_description).to eq 'Foo Bar Baz'
+    end
+
+    it 'truncates' do
+      helper.page_description <<-LOREM.strip_heredoc
+        Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo
+        ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis
+        dis parturient montes, nascetur ridiculus mus. Donec quam felis,
+        ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa
+        quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget,
+        arcu.
+      LOREM
+
+      expect(helper.page_description).to end_with 'quam felis,...'
+    end
+
+    it 'sanitizes all HTML' do
+      helper.page_description("<b>Bold</b> <h1>Header</h1>")
+
+      expect(helper.page_description).to eq 'Bold Header'
+    end
+  end
+
+  describe 'page_description_default' do
+    it 'uses Project description when available' do
+      project = double(description: 'Project Description')
+      helper.instance_variable_set(:@project, project)
+
+      expect(helper.page_description_default).to eq 'Project Description'
+    end
+
+    it 'uses brand_title when Project description is nil' do
+      project = double(description: nil)
+      helper.instance_variable_set(:@project, project)
+
+      expect(helper).to receive(:brand_title).and_return('Brand Title')
+      expect(helper.page_description_default).to eq 'Brand Title'
+    end
+
+    it 'falls back to brand_title' do
+      allow(helper).to receive(:brand_title).and_return('Brand Title')
+
+      expect(helper.page_description_default).to eq 'Brand Title'
+    end
+  end
+
+  describe 'page_image' do
+    it 'defaults to the GitLab logo' do
+      expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
+    end
+
+    context 'with @project' do
+      it 'uses Project avatar if available' do
+        project = double(avatar_url: 'http://example.com/uploads/avatar.png')
+        helper.instance_variable_set(:@project, project)
+
+        expect(helper.page_image).to eq project.avatar_url
+      end
+
+      it 'falls back to the default' do
+        project = double(avatar_url: nil)
+        helper.instance_variable_set(:@project, project)
+
+        expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
+      end
+    end
+
+    context 'with @user' do
+      it 'delegates to avatar_icon helper' do
+        user = double('User')
+        helper.instance_variable_set(:@user, user)
+
+        expect(helper).to receive(:avatar_icon).with(user)
+
+        helper.page_image
+      end
+    end
+  end
+
+  describe 'page_card_attributes' do
+    it 'raises ArgumentError when given more than two attributes' do
+      map = { foo: 'foo', bar: 'bar', baz: 'baz' }
+
+      expect { helper.page_card_attributes(map) }.
+        to raise_error(ArgumentError, /more than two attributes/)
+    end
+
+    it 'rejects blank values' do
+      map = { foo: 'foo', bar: '' }
+      helper.page_card_attributes(map)
+
+      expect(helper.page_card_attributes).to eq({ foo: 'foo' })
+    end
+  end
+
+  describe 'page_card_meta_tags' do
+    it 'returns the twitter:label and twitter:data tags' do
+      allow(helper).to receive(:page_card_attributes).and_return(foo: 'bar')
+
+      tags = helper.page_card_meta_tags
+
+      aggregate_failures do
+        expect(tags).to include %q(<meta property="twitter:label1" content="foo" />)
+        expect(tags).to include %q(<meta property="twitter:data1" content="bar" />)
+      end
+    end
+  end
+end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index f2efb528aeb6cc1c9397e7a5c9a8676286f4085d..53207767581e906005768c4e3313ea0beca51b35 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -53,6 +53,16 @@ describe ProjectsHelper do
     end
   end
 
+  describe 'user_max_access_in_project' do
+    let(:project) { create(:project) }
+    let(:user) { create(:user) }
+    before do
+      project.team.add_user(user, Gitlab::Access::MASTER)
+    end
+
+    it { expect(helper.user_max_access_in_project(user.id, project)).to eq('Master') }
+  end
+
   describe "readme_cache_key" do
     let(:project) { create(:project) }
 
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index c4f7693329cef299317a717c21379588a1edb51f..aafc24397a9af0ee35c12acd942d0a20090ccd6d 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -7,69 +7,52 @@ describe VisibilityLevelHelper do
     init_haml_helpers
   end
 
-  let(:project) { create(:project) }
+  let(:project)          { build(:project) }
+  let(:personal_snippet) { build(:personal_snippet) }
+  let(:project_snippet)  { build(:project_snippet) }
 
   describe 'visibility_level_description' do
-    shared_examples 'a visibility level description' do
-      let(:desc) do
-        visibility_level_description(Gitlab::VisibilityLevel::PRIVATE,
-                                     form_model)
-      end
-
-      let(:expected_class) do
-        class_name = case form_model.class.name
-                     when 'String'
-                       form_model
-                     else
-                       form_model.class.name
-                     end
-
-        class_name.match(/(project|snippet)$/i)[0]
-      end
-
-      it 'should refer to the correct class' do
-        expect(desc).to match(/#{expected_class}/i)
+    context 'used with a Project' do
+      it 'delegates projects to #project_visibility_level_description' do
+        expect(visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, project))
+            .to match /project/i
       end
     end
 
-    context 'form_model argument is a String' do
-      context 'model object is a personal snippet' do
-        it_behaves_like 'a visibility level description' do
-          let(:form_model) { 'PersonalSnippet' }
-        end
+    context 'called with a Snippet' do
+      it 'delegates snippets to #snippet_visibility_level_description' do
+        expect(visibility_level_description(Gitlab::VisibilityLevel::INTERNAL, project_snippet))
+            .to match /snippet/i
       end
+    end
+  end
 
-      context 'model object is a project snippet' do
-        it_behaves_like 'a visibility level description' do
-          let(:form_model) { 'ProjectSnippet' }
-        end
-      end
+  describe "#project_visibility_level_description" do
+    it "describes private projects" do
+      expect(project_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE))
+            .to eq "Project access must be granted explicitly to each user."
+    end
 
-      context 'model object is a project' do
-        it_behaves_like 'a visibility level description' do
-          let(:form_model) { 'Project' }
-        end
-      end
+    it "describes public projects" do
+      expect(project_visibility_level_description(Gitlab::VisibilityLevel::PUBLIC))
+            .to eq "The project can be cloned without any authentication."
     end
+  end
 
-    context 'form_model argument is a model object' do
-      context 'model object is a personal snippet' do
-        it_behaves_like 'a visibility level description' do
-          let(:form_model) { create(:personal_snippet) }
-        end
-      end
+  describe "#snippet_visibility_level_description" do
+    it 'describes visibility only for me' do
+      expect(snippet_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, personal_snippet))
+            .to eq "The snippet is visible only to me."
+    end
 
-      context 'model object is a project snippet' do
-        it_behaves_like 'a visibility level description' do
-          let(:form_model) { create(:project_snippet, project: project) }
-        end
-      end
+    it 'describes visibility for project members' do
+      expect(snippet_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, project_snippet))
+            .to eq "The snippet is visible only to project members."
+    end
 
-      context 'model object is a project' do
-        it_behaves_like 'a visibility level description' do
-          let(:form_model) { project }
-        end
-      end
+    it 'defaults to personal snippet' do
+      expect(snippet_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE))
+            .to eq "The snippet is visible only to me."
     end
   end
 
diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml
index 7e8b2a64351f176907213128c04d0f4af597ae0c..470cabeafbb90718ad18d14423ef334ac3c61894 100644
--- a/spec/javascripts/fixtures/issues_show.html.haml
+++ b/spec/javascripts/fixtures/issues_show.html.haml
@@ -1,6 +1,16 @@
-%a.btn-close
+:css
+  .hidden { display: none !important; }
 
-.issue-details
+.flash-container
+  .flash-alert
+  .flash-notice
+
+.status-box.status-box-open Open
+.status-box.status-box-closed.hidden Closed
+%a.btn-close{"href" => "http://gitlab.com/issues/6/close"} Close
+%a.btn-reopen.hidden{"href" => "http://gitlab.com/issues/6/reopen"} Reopen
+
+.detail-page-description
   .description.js-task-list-container
     .wiki
       %ul.task-list
diff --git a/spec/javascripts/fixtures/merge_request_tabs.html.haml b/spec/javascripts/fixtures/merge_request_tabs.html.haml
index 7624a713948aeea7de1a67e91b888727a275f9a2..68678c3d7e30396a5e71d6bdd520ba586dc9a661 100644
--- a/spec/javascripts/fixtures/merge_request_tabs.html.haml
+++ b/spec/javascripts/fixtures/merge_request_tabs.html.haml
@@ -1,12 +1,12 @@
 %ul.nav.nav-tabs.merge-request-tabs
   %li.notes-tab
-    %a{href: '/foo/bar/merge_requests/1', data: {target: '#notes', action: 'notes', toggle: 'tab'}}
+    %a{href: '/foo/bar/merge_requests/1', data: {target: 'div#notes', action: 'notes', toggle: 'tab'}}
       Discussion
   %li.commits-tab
-    %a{href: '/foo/bar/merge_requests/1/commits', data: {target: '#commits', action: 'commits', toggle: 'tab'}}
+    %a{href: '/foo/bar/merge_requests/1/commits', data: {target: 'div#commits', action: 'commits', toggle: 'tab'}}
       Commits
   %li.diffs-tab
-    %a{href: '/foo/bar/merge_requests/1/diffs', data: {target: '#diffs', action: 'diffs', toggle: 'tab'}}
+    %a{href: '/foo/bar/merge_requests/1/diffs', data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'}}
       Diffs
 
 .tab-content
diff --git a/spec/javascripts/fixtures/merge_requests_show.html.haml b/spec/javascripts/fixtures/merge_requests_show.html.haml
index f0c622935f849cc2680fa27b3534ec035b05c76b..8447dfdda3205e6ac5b25a6cb25da8ba7ba3bffa 100644
--- a/spec/javascripts/fixtures/merge_requests_show.html.haml
+++ b/spec/javascripts/fixtures/merge_requests_show.html.haml
@@ -1,6 +1,6 @@
 %a.btn-close
 
-.merge-request-details
+.detail-page-description
   .description.js-task-list-container
     .wiki
       %ul.task-list
diff --git a/spec/javascripts/fixtures/new_branch.html.haml b/spec/javascripts/fixtures/new_branch.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f06629e5eccd4ef5814c12e510479c3f0648af8d
--- /dev/null
+++ b/spec/javascripts/fixtures/new_branch.html.haml
@@ -0,0 +1,4 @@
+%form.js-create-branch-form
+  %input.js-branch-name
+  .js-branch-name-error
+  %input{id: "ref"}
diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
index 268e4c68c3150f230ffa44a949fcd2320ccd384d..7e67c77886109beb2b56c8b5c481744f16af3e16 100644
--- a/spec/javascripts/issue_spec.js.coffee
+++ b/spec/javascripts/issue_spec.js.coffee
@@ -20,3 +20,89 @@ describe 'Issue', ->
         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', ->
+    $.ajax = (obj) ->
+      expect(obj.type).toBe('PUT')
+      expect(obj.url).toBe('http://gitlab.com/issues/6/close')
+      obj.success saved: true
+    
+    $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 closes an issue with success:false', ->
+
+    $.ajax = (obj) ->
+      expect(obj.type).toBe('PUT')
+      expect(obj.url).toBe('http://goesnowhere.nothing/whereami')
+      obj.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', ->
+
+    $.ajax = (obj) ->
+      expect(obj.type).toBe('PUT')
+      expect(obj.url).toBe('http://goesnowhere.nothing/whereami')
+      obj.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', ->
+    $.ajax = (obj) ->
+      expect(obj.type).toBe('PUT')
+      expect(obj.url).toBe('http://gitlab.com/issues/6/reopen')
+      obj.success saved: true
+
+    $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()
\ No newline at end of file
diff --git a/spec/javascripts/new_branch_spec.js.coffee b/spec/javascripts/new_branch_spec.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..f2ce85efcdcbbaac28c81ec75867d3723f587833
--- /dev/null
+++ b/spec/javascripts/new_branch_spec.js.coffee
@@ -0,0 +1,160 @@
+#= require jquery-ui
+#= 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/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..81b9a513ce37f12ae72a6419bd6eb2bef81502b0
--- /dev/null
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Banzai::CrossProjectReference, lib: true do
+  include described_class
+
+  describe '#project_from_ref' do
+    context 'when no project was referenced' do
+      it 'returns the project from context' do
+        project = double
+
+        allow(self).to receive(:context).and_return({ project: project })
+
+        expect(project_from_ref(nil)).to eq project
+      end
+    end
+
+    context 'when referenced project does not exist' do
+      it 'returns nil' do
+        expect(project_from_ref('invalid/reference')).to be_nil
+      end
+    end
+
+    context 'when referenced project exists' do
+      it 'returns the referenced project' do
+        project2 = double('referenced project')
+
+        expect(Project).to receive(:find_with_namespace).
+          with('cross/reference').and_return(project2)
+
+        expect(project_from_ref('cross/reference')).to eq project2
+      end
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..84c2ddf444e6f071944825c7f7be554b3383f6f7
--- /dev/null
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -0,0 +1,112 @@
+require 'spec_helper'
+
+describe Banzai::Filter::AutolinkFilter, lib: true do
+  include FilterSpecHelper
+
+  let(:link) { 'http://about.gitlab.com/' }
+
+  it 'does nothing when :autolink is false' do
+    exp = act = link
+    expect(filter(act, autolink: false).to_html).to eq exp
+  end
+
+  it 'does nothing with non-link text' do
+    exp = act = 'This text contains no links to autolink'
+    expect(filter(act).to_html).to eq exp
+  end
+
+  context 'Rinku schemes' do
+    it 'autolinks http' do
+      doc = filter("See #{link}")
+      expect(doc.at_css('a').text).to eq link
+      expect(doc.at_css('a')['href']).to eq link
+    end
+
+    it 'autolinks https' do
+      link = 'https://google.com/'
+      doc = filter("See #{link}")
+
+      expect(doc.at_css('a').text).to eq link
+      expect(doc.at_css('a')['href']).to eq link
+    end
+
+    it 'autolinks ftp' do
+      link = 'ftp://ftp.us.debian.org/debian/'
+      doc = filter("See #{link}")
+
+      expect(doc.at_css('a').text).to eq link
+      expect(doc.at_css('a')['href']).to eq link
+    end
+
+    it 'autolinks short URLs' do
+      link = 'http://localhost:3000/'
+      doc = filter("See #{link}")
+
+      expect(doc.at_css('a').text).to eq link
+      expect(doc.at_css('a')['href']).to eq link
+    end
+
+    it 'accepts link_attr options' do
+      doc = filter("See #{link}", link_attr: { class: 'custom' })
+
+      expect(doc.at_css('a')['class']).to eq 'custom'
+    end
+
+    described_class::IGNORE_PARENTS.each do |elem|
+      it "ignores valid links contained inside '#{elem}' element" do
+        exp = act = "<#{elem}>See #{link}</#{elem}>"
+        expect(filter(act).to_html).to eq exp
+      end
+    end
+  end
+
+  context 'other schemes' do
+    let(:link) { 'foo://bar.baz/' }
+
+    it 'autolinks smb' do
+      link = 'smb:///Volumes/shared/foo.pdf'
+      doc = filter("See #{link}")
+
+      expect(doc.at_css('a').text).to eq link
+      expect(doc.at_css('a')['href']).to eq link
+    end
+
+    it 'autolinks irc' do
+      link = 'irc://irc.freenode.net/git'
+      doc = filter("See #{link}")
+
+      expect(doc.at_css('a').text).to eq link
+      expect(doc.at_css('a')['href']).to eq link
+    end
+
+    it 'does not include trailing punctuation' do
+      doc = filter("See #{link}.")
+      expect(doc.at_css('a').text).to eq link
+
+      doc = filter("See #{link}, ok?")
+      expect(doc.at_css('a').text).to eq link
+
+      doc = filter("See #{link}...")
+      expect(doc.at_css('a').text).to eq link
+    end
+
+    it 'does not include trailing HTML entities' do
+      doc = filter("See &lt;&lt;&lt;#{link}&gt;&gt;&gt;")
+
+      expect(doc.at_css('a')['href']).to eq link
+      expect(doc.text).to eq "See <<<#{link}>>>"
+    end
+
+    it 'accepts link_attr options' do
+      doc = filter("See #{link}", link_attr: { class: 'custom' })
+      expect(doc.at_css('a')['class']).to eq 'custom'
+    end
+
+    described_class::IGNORE_PARENTS.each do |elem|
+      it "ignores valid links contained inside '#{elem}' element" do
+        exp = act = "<#{elem}>See #{link}</#{elem}>"
+        expect(filter(act).to_html).to eq exp
+      end
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c2a8ad36c3057fd4acd06cb329f6ead560b288bd
--- /dev/null
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -0,0 +1,182 @@
+require 'spec_helper'
+
+describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
+  include FilterSpecHelper
+
+  let(:project) { create(:project, :public) }
+  let(:commit1) { project.commit("HEAD~2") }
+  let(:commit2) { project.commit }
+
+  let(:range)  { CommitRange.new("#{commit1.id}...#{commit2.id}", project) }
+  let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}", project) }
+
+  it 'requires project context' do
+    expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
+  end
+
+  %w(pre code a style).each do |elem|
+    it "ignores valid references contained inside '#{elem}' element" do
+      exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>"
+      expect(reference_filter(act).to_html).to eq exp
+    end
+  end
+
+  context 'internal reference' do
+    let(:reference)  { range.to_reference }
+    let(:reference2) { range2.to_reference }
+
+    it 'links to a valid two-dot reference' do
+      doc = reference_filter("See #{reference2}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
+    end
+
+    it 'links to a valid three-dot reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
+    end
+
+    it 'links to a valid short ID' do
+      reference = "#{commit1.short_id}...#{commit2.id}"
+      reference2 = "#{commit1.id}...#{commit2.short_id}"
+
+      exp = commit1.short_id + '...' + commit2.short_id
+
+      expect(reference_filter("See #{reference}").css('a').first.text).to eq exp
+      expect(reference_filter("See #{reference2}").css('a').first.text).to eq exp
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("See (#{reference}.)")
+
+      exp = Regexp.escape(range.reference_link_text)
+      expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid commit IDs' do
+      exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
+
+      expect(project).to receive(:valid_repo?).and_return(true)
+      expect(project.repository).to receive(:commit).with(commit1.id.reverse)
+      expect(project.repository).to receive(:commit).with(commit2.id)
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'includes a title attribute' do
+      doc = reference_filter("See #{reference}")
+      expect(doc.css('a').first.attr('title')).to eq range.reference_title
+    end
+
+    it 'includes default classes' do
+      doc = reference_filter("See #{reference}")
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
+    end
+
+    it 'includes a data-project attribute' do
+      doc = reference_filter("See #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-project')
+      expect(link.attr('data-project')).to eq project.id.to_s
+    end
+
+    it 'includes a data-commit-range attribute' do
+      doc = reference_filter("See #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-commit-range')
+      expect(link.attr('data-commit-range')).to eq range.to_s
+    end
+
+    it 'supports an :only_path option' do
+      doc = reference_filter("See #{reference}", only_path: true)
+      link = doc.css('a').first.attr('href')
+
+      expect(link).not_to match %r(https?://)
+      expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true)
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("See #{reference}")
+      expect(result[:references][:commit_range]).not_to be_empty
+    end
+  end
+
+  context 'cross-project reference' do
+    let(:namespace) { create(:namespace, name: 'cross-reference') }
+    let(:project2)  { create(:project, :public, namespace: namespace) }
+    let(:reference) { range.to_reference(project) }
+
+    before do
+      range.project = project2
+    end
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Fixed (#{reference}.)")
+
+      exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}")
+      expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid commit IDs on the referenced project' do
+      exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
+      expect(reference_filter(act).to_html).to eq exp
+
+      exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("See #{reference}")
+      expect(result[:references][:commit_range]).not_to be_empty
+    end
+  end
+
+  context 'cross-project URL reference' do
+    let(:namespace) { create(:namespace, name: 'cross-reference') }
+    let(:project2)  { create(:project, :public, namespace: namespace) }
+    let(:range)  { CommitRange.new("#{commit1.id}...master", project) }
+    let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') }
+
+    before do
+      range.project = project2
+    end
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq reference
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Fixed (#{reference}.)")
+
+      exp = Regexp.escape(range.reference_link_text(project))
+      expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid commit IDs on the referenced project' do
+      exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
+      expect(reference_filter(act).to_html).to eq exp
+
+      exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("See #{reference}")
+      expect(result[:references][:commit_range]).not_to be_empty
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..473534ba68a66d26a5e0afd5e22ad8ad169f3271
--- /dev/null
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -0,0 +1,163 @@
+require 'spec_helper'
+
+describe Banzai::Filter::CommitReferenceFilter, lib: true do
+  include FilterSpecHelper
+
+  let(:project) { create(:project, :public) }
+  let(:commit)  { project.commit }
+
+  it 'requires project context' do
+    expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
+  end
+
+  %w(pre code a style).each do |elem|
+    it "ignores valid references contained inside '#{elem}' element" do
+      exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>"
+      expect(reference_filter(act).to_html).to eq exp
+    end
+  end
+
+  context 'internal reference' do
+    let(:reference) { commit.id }
+
+    # Let's test a variety of commit SHA sizes just to be paranoid
+    [6, 8, 12, 18, 20, 32, 40].each do |size|
+      it "links to a valid reference of #{size} characters" do
+        doc = reference_filter("See #{reference[0...size]}")
+
+        expect(doc.css('a').first.text).to eq commit.short_id
+        expect(doc.css('a').first.attr('href')).
+          to eq urls.namespace_project_commit_url(project.namespace, project, reference)
+      end
+    end
+
+    it 'always uses the short ID as the link text' do
+      doc = reference_filter("See #{commit.id}")
+      expect(doc.text).to eq "See #{commit.short_id}"
+
+      doc = reference_filter("See #{commit.id[0...6]}")
+      expect(doc.text).to eq "See #{commit.short_id}"
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("See (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid commit IDs' do
+      invalid = invalidate_reference(reference)
+      exp = act = "See #{invalid}"
+
+      expect(project).to receive(:valid_repo?).and_return(true)
+      expect(project.repository).to receive(:commit).with(invalid)
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'includes a title attribute' do
+      doc = reference_filter("See #{reference}")
+      expect(doc.css('a').first.attr('title')).to eq commit.link_title
+    end
+
+    it 'escapes the title attribute' do
+      allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="})
+
+      doc = reference_filter("See #{reference}")
+      expect(doc.text).to eq "See #{commit.short_id}"
+    end
+
+    it 'includes default classes' do
+      doc = reference_filter("See #{reference}")
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
+    end
+
+    it 'includes a data-project attribute' do
+      doc = reference_filter("See #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-project')
+      expect(link.attr('data-project')).to eq project.id.to_s
+    end
+
+    it 'includes a data-commit attribute' do
+      doc = reference_filter("See #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-commit')
+      expect(link.attr('data-commit')).to eq commit.id
+    end
+
+    it 'supports an :only_path context' do
+      doc = reference_filter("See #{reference}", only_path: true)
+      link = doc.css('a').first.attr('href')
+
+      expect(link).not_to match %r(https?://)
+      expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true)
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("See #{reference}")
+      expect(result[:references][:commit]).not_to be_empty
+    end
+  end
+
+  context 'cross-project reference' do
+    let(:namespace) { create(:namespace, name: 'cross-reference') }
+    let(:project2)  { create(:project, :public, namespace: namespace) }
+    let(:commit)    { project2.commit }
+    let(:reference) { commit.to_reference(project) }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Fixed (#{reference}.)")
+
+      exp = Regexp.escape(project2.to_reference)
+      expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid commit IDs on the referenced project' do
+      exp = act = "Committed #{invalidate_reference(reference)}"
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("See #{reference}")
+      expect(result[:references][:commit]).not_to be_empty
+    end
+  end
+
+  context 'cross-project URL reference' do
+    let(:namespace) { create(:namespace, name: 'cross-reference') }
+    let(:project2)  { create(:project, :public, namespace: namespace) }
+    let(:commit)    { project2.commit }
+    let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Fixed (#{reference}.)")
+
+      expect(doc.to_html).to match(/\(<a.+>#{commit.reference_link_text(project)}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid commit IDs on the referenced project' do
+      act = "Committed #{invalidate_reference(reference)}"
+      expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("See #{reference}")
+      expect(result[:references][:commit]).not_to be_empty
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cf3140581584d45b8f8c34acecb0ff69dc9af141
--- /dev/null
+++ b/spec/lib/banzai/filter/emoji_filter_spec.rb
@@ -0,0 +1,98 @@
+require 'spec_helper'
+
+describe Banzai::Filter::EmojiFilter, lib: true do
+  include FilterSpecHelper
+
+  before do
+    @original_asset_host = ActionController::Base.asset_host
+    ActionController::Base.asset_host = 'https://foo.com'
+  end
+
+  after do
+    ActionController::Base.asset_host = @original_asset_host
+  end
+
+  it 'replaces supported emoji' do
+    doc = filter('<p>:heart:</p>')
+    expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png'
+  end
+
+  it 'ignores unsupported emoji' do
+    exp = act = '<p>:foo:</p>'
+    doc = filter(act)
+    expect(doc.to_html).to match Regexp.escape(exp)
+  end
+
+  it 'correctly encodes the URL' do
+    doc = filter('<p>:+1:</p>')
+    expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png'
+  end
+
+  it 'matches at the start of a string' do
+    doc = filter(':+1:')
+    expect(doc.css('img').size).to eq 1
+  end
+
+  it 'matches at the end of a string' do
+    doc = filter('This gets a :-1:')
+    expect(doc.css('img').size).to eq 1
+  end
+
+  it 'matches with adjacent text' do
+    doc = filter('+1 (:+1:)')
+    expect(doc.css('img').size).to eq 1
+  end
+
+  it 'matches multiple emoji in a row' do
+    doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
+    expect(doc.css('img').size).to eq 3
+  end
+
+  it 'has a title attribute' do
+    doc = filter(':-1:')
+    expect(doc.css('img').first.attr('title')).to eq ':-1:'
+  end
+
+  it 'has an alt attribute' do
+    doc = filter(':-1:')
+    expect(doc.css('img').first.attr('alt')).to eq ':-1:'
+  end
+
+  it 'has an align attribute' do
+    doc = filter(':8ball:')
+    expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
+  end
+
+  it 'has an emoji class' do
+    doc = filter(':cat:')
+    expect(doc.css('img').first.attr('class')).to eq 'emoji'
+  end
+
+  it 'has height and width attributes' do
+    doc = filter(':dog:')
+    img = doc.css('img').first
+
+    expect(img.attr('width')).to eq '20'
+    expect(img.attr('height')).to eq '20'
+  end
+
+  it 'keeps whitespace intact' do
+    doc = filter('This deserves a :+1:, big time.')
+
+    expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
+  end
+
+  it 'uses a custom asset_root context' do
+    root = Gitlab.config.gitlab.url + 'gitlab/root'
+
+    doc = filter(':smile:', asset_root: root)
+    expect(doc.css('img').first.attr('src')).to start_with(root)
+  end
+
+  it 'uses a custom asset_host context' do
+    ActionController::Base.asset_host = 'https://cdn.example.com'
+
+    doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?')
+    expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
+  end
+end
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..953466679e4beb276f65b5a1a6adb06f01d91b6f
--- /dev/null
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
+  include FilterSpecHelper
+
+  def helper
+    IssuesHelper
+  end
+
+  let(:project) { create(:jira_project) }
+
+  context 'JIRA issue references' do
+    let(:issue)     { ExternalIssue.new('JIRA-123', project) }
+    let(:reference) { issue.to_reference }
+
+    it 'requires project context' do
+      expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
+    end
+
+    %w(pre code a style).each do |elem|
+      it "ignores valid references contained inside '#{elem}' element" do
+        exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
+        expect(filter(act).to_html).to eq exp
+      end
+    end
+
+    it 'ignores valid references when using default tracker' do
+      expect(project).to receive(:default_issues_tracker?).and_return(true)
+
+      exp = act = "Issue #{reference}"
+      expect(filter(act).to_html).to eq exp
+    end
+
+    it 'links to a valid reference' do
+      doc = filter("Issue #{reference}")
+      expect(doc.css('a').first.attr('href'))
+        .to eq helper.url_for_issue(reference, project)
+    end
+
+    it 'links to the external tracker' do
+      doc = filter("Issue #{reference}")
+      link = doc.css('a').first.attr('href')
+
+      expect(link).to eq "http://jira.example/browse/#{reference}"
+    end
+
+    it 'links with adjacent text' do
+      doc = filter("Issue (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
+    end
+
+    it 'includes a title attribute' do
+      doc = filter("Issue #{reference}")
+      expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker"
+    end
+
+    it 'escapes the title attribute' do
+      allow(project.external_issue_tracker).to receive(:title).
+        and_return(%{"></a>whatever<a title="})
+
+      doc = filter("Issue #{reference}")
+      expect(doc.text).to eq "Issue #{reference}"
+    end
+
+    it 'includes default classes' do
+      doc = filter("Issue #{reference}")
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
+    end
+
+    it 'supports an :only_path context' do
+      doc = filter("Issue #{reference}", only_path: true)
+      link = doc.css('a').first.attr('href')
+
+      expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true)
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e3a8e15330e228f448986f6c0615fff6a4dcea72
--- /dev/null
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Banzai::Filter::ExternalLinkFilter, lib: true do
+  include FilterSpecHelper
+
+  it 'ignores elements without an href attribute' do
+    exp = act = %q(<a id="ignored">Ignore Me</a>)
+    expect(filter(act).to_html).to eq exp
+  end
+
+  it 'ignores non-HTTP(S) links' do
+    exp = act = %q(<a href="irc://irc.freenode.net/gitlab">IRC</a>)
+    expect(filter(act).to_html).to eq exp
+  end
+
+  it 'skips internal links' do
+    internal = Gitlab.config.gitlab.url
+    exp = act = %Q(<a href="#{internal}/sign_in">Login</a>)
+    expect(filter(act).to_html).to eq exp
+  end
+
+  it 'adds rel="nofollow" to external links' do
+    act = %q(<a href="https://google.com/">Google</a>)
+    doc = filter(act)
+
+    expect(doc.at_css('a')).to have_attribute('rel')
+    expect(doc.at_css('a')['rel']).to eq 'nofollow'
+  end
+end
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5a0d3d577a83294ef014a04da2bf1f799fb660ac
--- /dev/null
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -0,0 +1,209 @@
+require 'spec_helper'
+
+describe Banzai::Filter::IssueReferenceFilter, lib: true do
+  include FilterSpecHelper
+
+  def helper
+    IssuesHelper
+  end
+
+  let(:project) { create(:empty_project, :public) }
+  let(:issue)   { create(:issue, project: project) }
+
+  it 'requires project context' do
+    expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
+  end
+
+  %w(pre code a style).each do |elem|
+    it "ignores valid references contained inside '#{elem}' element" do
+      exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>"
+      expect(reference_filter(act).to_html).to eq exp
+    end
+  end
+
+  context 'internal reference' do
+    let(:reference) { issue.to_reference }
+
+    it 'ignores valid references when using non-default tracker' do
+      expect(project).to receive(:get_issue).with(issue.iid).and_return(nil)
+
+      exp = act = "Issue #{reference}"
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'links to a valid reference' do
+      doc = reference_filter("Fixed #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq helper.url_for_issue(issue.iid, project)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Fixed (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid issue IDs' do
+      invalid = invalidate_reference(reference)
+      exp = act = "Fixed #{invalid}"
+
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'includes a title attribute' do
+      doc = reference_filter("Issue #{reference}")
+      expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
+    end
+
+    it 'escapes the title attribute' do
+      issue.update_attribute(:title, %{"></a>whatever<a title="})
+
+      doc = reference_filter("Issue #{reference}")
+      expect(doc.text).to eq "Issue #{reference}"
+    end
+
+    it 'includes default classes' do
+      doc = reference_filter("Issue #{reference}")
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
+    end
+
+    it 'includes a data-project attribute' do
+      doc = reference_filter("Issue #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-project')
+      expect(link.attr('data-project')).to eq project.id.to_s
+    end
+
+    it 'includes a data-issue attribute' do
+      doc = reference_filter("See #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-issue')
+      expect(link.attr('data-issue')).to eq issue.id.to_s
+    end
+
+    it 'supports an :only_path context' do
+      doc = reference_filter("Issue #{reference}", only_path: true)
+      link = doc.css('a').first.attr('href')
+
+      expect(link).not_to match %r(https?://)
+      expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true)
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Fixed #{reference}")
+      expect(result[:references][:issue]).to eq [issue]
+    end
+  end
+
+  context 'cross-project reference' do
+    let(:namespace) { create(:namespace, name: 'cross-reference') }
+    let(:project2)  { create(:empty_project, :public, namespace: namespace) }
+    let(:issue)     { create(:issue, project: project2) }
+    let(:reference) { issue.to_reference(project) }
+
+    it 'ignores valid references when cross-reference project uses external tracker' do
+      expect_any_instance_of(Project).to receive(:get_issue).
+        with(issue.iid).and_return(nil)
+
+      exp = act = "Issue #{reference}"
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq helper.url_for_issue(issue.iid, project2)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Fixed (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid issue IDs on the referenced project' do
+      exp = act = "Fixed #{invalidate_reference(reference)}"
+
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Fixed #{reference}")
+      expect(result[:references][:issue]).to eq [issue]
+    end
+  end
+
+  context 'cross-project URL reference' do
+    let(:namespace) { create(:namespace, name: 'cross-reference') }
+    let(:project2)  { create(:empty_project, :public, namespace: namespace) }
+    let(:issue)     { create(:issue, project: project2) }
+    let(:reference) { helper.url_for_issue(issue.iid, project2) + "#note_123" }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq reference
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Fixed (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/)
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Fixed #{reference}")
+      expect(result[:references][:issue]).to eq [issue]
+    end
+  end
+
+  context 'cross-project reference in link href' do
+    let(:namespace) { create(:namespace, name: 'cross-reference') }
+    let(:project2)  { create(:empty_project, :public, namespace: namespace) }
+    let(:issue)     { create(:issue, project: project2) }
+    let(:reference) { %Q{<a href="#{issue.to_reference(project)}">Reference</a>} }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq helper.url_for_issue(issue.iid, project2)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Fixed (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Fixed #{reference}")
+      expect(result[:references][:issue]).to eq [issue]
+    end
+  end
+
+  context 'cross-project URL in link href' do
+    let(:namespace) { create(:namespace, name: 'cross-reference') }
+    let(:project2)  { create(:empty_project, :public, namespace: namespace) }
+    let(:issue)     { create(:issue, project: project2) }
+    let(:reference) { %Q{<a href="#{helper.url_for_issue(issue.iid, project2) + "#note_123"}">Reference</a>} }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq helper.url_for_issue(issue.iid, project2) + "#note_123"
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Fixed (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Fixed #{reference}")
+      expect(result[:references][:issue]).to eq [issue]
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b46ccc4760509f809da2a597e474c4ecfff72537
--- /dev/null
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -0,0 +1,179 @@
+require 'spec_helper'
+require 'html/pipeline'
+
+describe Banzai::Filter::LabelReferenceFilter, lib: true do
+  include FilterSpecHelper
+
+  let(:project)   { create(:empty_project, :public) }
+  let(:label)     { create(:label, project: project) }
+  let(:reference) { label.to_reference }
+
+  it 'requires project context' do
+    expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
+  end
+
+  %w(pre code a style).each do |elem|
+    it "ignores valid references contained inside '#{elem}' element" do
+      exp = act = "<#{elem}>Label #{reference}</#{elem}>"
+      expect(reference_filter(act).to_html).to eq exp
+    end
+  end
+
+  it 'includes default classes' do
+    doc = reference_filter("Label #{reference}")
+    expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
+  end
+
+  it 'includes a data-project attribute' do
+    doc = reference_filter("Label #{reference}")
+    link = doc.css('a').first
+
+    expect(link).to have_attribute('data-project')
+    expect(link.attr('data-project')).to eq project.id.to_s
+  end
+
+  it 'includes a data-label attribute' do
+    doc = reference_filter("See #{reference}")
+    link = doc.css('a').first
+
+    expect(link).to have_attribute('data-label')
+    expect(link.attr('data-label')).to eq label.id.to_s
+  end
+
+  it 'supports an :only_path context' do
+    doc = reference_filter("Label #{reference}", only_path: true)
+    link = doc.css('a').first.attr('href')
+
+    expect(link).not_to match %r(https?://)
+    expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name)
+  end
+
+  it 'adds to the results hash' do
+    result = reference_pipeline_result("Label #{reference}")
+    expect(result[:references][:label]).to eq [label]
+  end
+
+  describe 'label span element' do
+    it 'includes default classes' do
+      doc = reference_filter("Label #{reference}")
+      expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
+    end
+
+    it 'includes a style attribute' do
+      doc = reference_filter("Label #{reference}")
+      expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/)
+    end
+  end
+
+  context 'Integer-based references' do
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).to eq urls.
+        namespace_project_issues_url(project.namespace, project, label_name: label.name)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Label (#{reference}.)")
+      expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+    end
+
+    it 'ignores invalid label IDs' do
+      exp = act = "Label #{invalidate_reference(reference)}"
+
+      expect(reference_filter(act).to_html).to eq exp
+    end
+  end
+
+  context 'String-based single-word references' do
+    let(:label)     { create(:label, name: 'gfm', project: project) }
+    let(:reference) { "#{Label.reference_prefix}#{label.name}" }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).to eq urls.
+        namespace_project_issues_url(project.namespace, project, label_name: label.name)
+      expect(doc.text).to eq 'See gfm'
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Label (#{reference}.)")
+      expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+    end
+
+    it 'ignores invalid label names' do
+      exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}"
+
+      expect(reference_filter(act).to_html).to eq exp
+    end
+  end
+
+  context 'String-based multi-word references in quotes' do
+    let(:label)     { create(:label, name: 'gfm references', project: project) }
+    let(:reference) { label.to_reference(:name) }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).to eq urls.
+        namespace_project_issues_url(project.namespace, project, label_name: label.name)
+      expect(doc.text).to eq 'See gfm references'
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Label (#{reference}.)")
+      expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+    end
+
+    it 'ignores invalid label names' do
+      exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
+
+      expect(reference_filter(act).to_html).to eq exp
+    end
+  end
+
+  describe 'edge cases' do
+    it 'gracefully handles non-references matching the pattern' do
+      exp = act = '(format nil "~0f" 3.0) ; 3.0'
+      expect(reference_filter(act).to_html).to eq exp
+    end
+  end
+
+  describe 'referencing a label in a link href' do
+    let(:reference) { %Q{<a href="#{label.to_reference}">Label</a>} }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).to eq urls.
+        namespace_project_issues_url(project.namespace, project, label_name: label.name)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Label (#{reference}.)")
+      expect(doc.to_html).to match(%r(\(<a.+>Label</a>\.\)))
+    end
+
+    it 'includes a data-project attribute' do
+      doc = reference_filter("Label #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-project')
+      expect(link.attr('data-project')).to eq project.id.to_s
+    end
+
+    it 'includes a data-label attribute' do
+      doc = reference_filter("See #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-label')
+      expect(link.attr('data-label')).to eq label.id.to_s
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Label #{reference}")
+      expect(result[:references][:label]).to eq [label]
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..352710df3071fcc7949ba5ca3452a32eec018786
--- /dev/null
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -0,0 +1,142 @@
+require 'spec_helper'
+
+describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
+  include FilterSpecHelper
+
+  let(:project) { create(:project, :public) }
+  let(:merge)   { create(:merge_request, source_project: project) }
+
+  it 'requires project context' do
+    expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
+  end
+
+  %w(pre code a style).each do |elem|
+    it "ignores valid references contained inside '#{elem}' element" do
+      exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>"
+      expect(reference_filter(act).to_html).to eq exp
+    end
+  end
+
+  context 'internal reference' do
+    let(:reference) { merge.to_reference }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).to eq urls.
+        namespace_project_merge_request_url(project.namespace, project, merge)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Merge (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid merge IDs' do
+      exp = act = "Merge #{invalidate_reference(reference)}"
+
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'includes a title attribute' do
+      doc = reference_filter("Merge #{reference}")
+      expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
+    end
+
+    it 'escapes the title attribute' do
+      merge.update_attribute(:title, %{"></a>whatever<a title="})
+
+      doc = reference_filter("Merge #{reference}")
+      expect(doc.text).to eq "Merge #{reference}"
+    end
+
+    it 'includes default classes' do
+      doc = reference_filter("Merge #{reference}")
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
+    end
+
+    it 'includes a data-project attribute' do
+      doc = reference_filter("Merge #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-project')
+      expect(link.attr('data-project')).to eq project.id.to_s
+    end
+
+    it 'includes a data-merge-request attribute' do
+      doc = reference_filter("See #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-merge-request')
+      expect(link.attr('data-merge-request')).to eq merge.id.to_s
+    end
+
+    it 'supports an :only_path context' do
+      doc = reference_filter("Merge #{reference}", only_path: true)
+      link = doc.css('a').first.attr('href')
+
+      expect(link).not_to match %r(https?://)
+      expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true)
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Merge #{reference}")
+      expect(result[:references][:merge_request]).to eq [merge]
+    end
+  end
+
+  context 'cross-project reference' do
+    let(:namespace) { create(:namespace, name: 'cross-reference') }
+    let(:project2)  { create(:project, :public, namespace: namespace) }
+    let(:merge)     { create(:merge_request, source_project: project2) }
+    let(:reference) { merge.to_reference(project) }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq urls.namespace_project_merge_request_url(project2.namespace,
+                                                      project, merge)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Merge (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid merge IDs on the referenced project' do
+      exp = act = "Merge #{invalidate_reference(reference)}"
+
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Merge #{reference}")
+      expect(result[:references][:merge_request]).to eq [merge]
+    end
+  end
+
+  context 'cross-project URL reference' do
+    let(:namespace) { create(:namespace, name: 'cross-reference') }
+    let(:project2)  { create(:project, :public, namespace: namespace) }
+    let(:merge)     { create(:merge_request, source_project: project2, target_project: project2) }
+    let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq reference
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Merge (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/)
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Merge #{reference}")
+      expect(result[:references][:merge_request]).to eq [merge]
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e9bb388e361b44743f791ad087411799049df26b
--- /dev/null
+++ b/spec/lib/banzai/filter/redactor_filter_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe Banzai::Filter::RedactorFilter, lib: true do
+  include ActionView::Helpers::UrlHelper
+  include FilterSpecHelper
+
+  it 'ignores non-GFM links' do
+    html = %(See <a href="https://google.com/">Google</a>)
+    doc = filter(html, current_user: double)
+
+    expect(doc.css('a').length).to eq 1
+  end
+
+  def reference_link(data)
+    link_to('text', '', class: 'gfm', data: data)
+  end
+
+  context 'with data-project' do
+    it 'removes unpermitted Project references' do
+      user = create(:user)
+      project = create(:empty_project)
+
+      link = reference_link(project: project.id, reference_filter: 'ReferenceFilter')
+      doc = filter(link, current_user: user)
+
+      expect(doc.css('a').length).to eq 0
+    end
+
+    it 'allows permitted Project references' do
+      user = create(:user)
+      project = create(:empty_project)
+      project.team << [user, :master]
+
+      link = reference_link(project: project.id, reference_filter: 'ReferenceFilter')
+      doc = filter(link, current_user: user)
+
+      expect(doc.css('a').length).to eq 1
+    end
+
+    it 'handles invalid Project references' do
+      link = reference_link(project: 12345, reference_filter: 'ReferenceFilter')
+
+      expect { filter(link) }.not_to raise_error
+    end
+  end
+
+  context "for user references" do
+
+    context 'with data-group' do
+      it 'removes unpermitted Group references' do
+        user = create(:user)
+        group = create(:group)
+
+        link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter')
+        doc = filter(link, current_user: user)
+
+        expect(doc.css('a').length).to eq 0
+      end
+
+      it 'allows permitted Group references' do
+        user = create(:user)
+        group = create(:group)
+        group.add_developer(user)
+
+        link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter')
+        doc = filter(link, current_user: user)
+
+        expect(doc.css('a').length).to eq 1
+      end
+
+      it 'handles invalid Group references' do
+        link = reference_link(group: 12345, reference_filter: 'UserReferenceFilter')
+
+        expect { filter(link) }.not_to raise_error
+      end
+    end
+
+    context 'with data-user' do
+      it 'allows any User reference' do
+        user = create(:user)
+
+        link = reference_link(user: user.id, reference_filter: 'UserReferenceFilter')
+        doc = filter(link)
+
+        expect(doc.css('a').length).to eq 1
+      end
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/reference_gatherer_filter_spec.rb b/spec/lib/banzai/filter/reference_gatherer_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c8b1dfdf9448d5e4a5df45968e7db0d8c0e0b2d5
--- /dev/null
+++ b/spec/lib/banzai/filter/reference_gatherer_filter_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+describe Banzai::Filter::ReferenceGathererFilter, lib: true do
+  include ActionView::Helpers::UrlHelper
+  include FilterSpecHelper
+
+  def reference_link(data)
+    link_to('text', '', class: 'gfm', data: data)
+  end
+
+  context "for issue references" do
+
+    context 'with data-project' do
+      it 'removes unpermitted Project references' do
+        user = create(:user)
+        project = create(:empty_project)
+        issue = create(:issue, project: project)
+
+        link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+        result = pipeline_result(link, current_user: user)
+
+        expect(result[:references][:issue]).to be_empty
+      end
+
+      it 'allows permitted Project references' do
+        user = create(:user)
+        project = create(:empty_project)
+        issue = create(:issue, project: project)
+        project.team << [user, :master]
+
+        link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+        result = pipeline_result(link, current_user: user)
+
+        expect(result[:references][:issue]).to eq([issue])
+      end
+
+      it 'handles invalid Project references' do
+        link = reference_link(project: 12345, issue: 12345, reference_filter: 'IssueReferenceFilter')
+
+        expect { pipeline_result(link) }.not_to raise_error
+      end
+    end
+  end
+
+  context "for user references" do
+
+    context 'with data-group' do
+      it 'removes unpermitted Group references' do
+        user = create(:user)
+        group = create(:group)
+
+        link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter')
+        result = pipeline_result(link, current_user: user)
+
+        expect(result[:references][:user]).to be_empty
+      end
+
+      it 'allows permitted Group references' do
+        user = create(:user)
+        group = create(:group)
+        group.add_developer(user)
+
+        link = reference_link(group: group.id, reference_filter: 'UserReferenceFilter')
+        result = pipeline_result(link, current_user: user)
+
+        expect(result[:references][:user]).to eq([user])
+      end
+
+      it 'handles invalid Group references' do
+        link = reference_link(group: 12345, reference_filter: 'UserReferenceFilter')
+
+        expect { pipeline_result(link) }.not_to raise_error
+      end
+    end
+
+    context 'with data-user' do
+      it 'allows any User reference' do
+        user = create(:user)
+
+        link = reference_link(user: user.id, reference_filter: 'UserReferenceFilter')
+        result = pipeline_result(link)
+
+        expect(result[:references][:user]).to eq([user])
+      end
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0b3e5ecbc9fc3c929682e3f79a533cffdf2e0ac8
--- /dev/null
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -0,0 +1,147 @@
+# encoding: UTF-8
+
+require 'spec_helper'
+
+describe Banzai::Filter::RelativeLinkFilter, lib: true do
+  def filter(doc, contexts = {})
+    contexts.reverse_merge!({
+      commit:         project.commit,
+      project:        project,
+      project_wiki:   project_wiki,
+      ref:            ref,
+      requested_path: requested_path
+    })
+
+    described_class.call(doc, contexts)
+  end
+
+  def image(path)
+    %(<img src="#{path}" />)
+  end
+
+  def link(path)
+    %(<a href="#{path}">#{path}</a>)
+  end
+
+  let(:project)        { create(:project) }
+  let(:project_path)   { project.path_with_namespace }
+  let(:ref)            { 'markdown' }
+  let(:project_wiki)   { nil }
+  let(:requested_path) { '/' }
+
+  shared_examples :preserve_unchanged do
+    it 'does not modify any relative URL in anchor' do
+      doc = filter(link('README.md'))
+      expect(doc.at_css('a')['href']).to eq 'README.md'
+    end
+
+    it 'does not modify any relative URL in image' do
+      doc = filter(image('files/images/logo-black.png'))
+      expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
+    end
+  end
+
+  shared_examples :relative_to_requested do
+    it 'rebuilds URL relative to the requested path' do
+      doc = filter(link('users.md'))
+      expect(doc.at_css('a')['href']).
+        to eq "/#{project_path}/blob/#{ref}/doc/api/users.md"
+    end
+  end
+
+  context 'with a project_wiki' do
+    let(:project_wiki) { double('ProjectWiki') }
+    include_examples :preserve_unchanged
+  end
+
+  context 'without a repository' do
+    let(:project) { create(:empty_project) }
+    include_examples :preserve_unchanged
+  end
+
+  context 'with an empty repository' do
+    let(:project) { create(:project_empty_repo) }
+    include_examples :preserve_unchanged
+  end
+
+  it 'does not raise an exception on invalid URIs' do
+    act = link("://foo")
+    expect { filter(act) }.not_to raise_error
+  end
+
+  context 'with a valid repository' do
+    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 up one directory' do
+      relative_link = link('../api/README.md')
+      doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.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 multiple directories' do
+      relative_link = link('../../../api/README.md')
+      doc = filter(relative_link, requested_path: 'doc/foo/bar/baz/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 an anchor' do
+      doc = filter(link('README.md#section'))
+      expect(doc.at_css('a')['href']).
+        to eq "/#{project_path}/blob/#{ref}/README.md#section"
+    end
+
+    it 'rebuilds relative URL for a directory in the repo' do
+      doc = filter(link('doc/api/'))
+      expect(doc.at_css('a')['href']).
+        to eq "/#{project_path}/tree/#{ref}/doc/api"
+    end
+
+    it 'rebuilds relative URL for 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 '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'
+    end
+
+    it 'does not modify absolute URL' do
+      doc = filter(link('http://example.com'))
+      expect(doc.at_css('a')['href']).to eq 'http://example.com'
+    end
+
+    it 'supports Unicode filenames' do
+      path = 'files/images/한글.png'
+      escaped = Addressable::URI.escape(path)
+
+      # Stub these methods so the file doesn't actually need to be in the repo
+      allow_any_instance_of(described_class).
+        to receive(:file_exists?).and_return(true)
+      allow_any_instance_of(described_class).
+        to receive(:image?).with(path).and_return(true)
+
+      doc = filter(image(escaped))
+      expect(doc.at_css('img')['src']).to match '/raw/'
+    end
+
+    context 'when requested path is a file in the repo' do
+      let(:requested_path) { 'doc/api/README.md' }
+      include_examples :relative_to_requested
+    end
+
+    context 'when requested path is a directory in the repo' do
+      let(:requested_path) { 'doc/api' }
+      include_examples :relative_to_requested
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..760d60a41908cedc8d72a04bef89e944034f0414
--- /dev/null
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -0,0 +1,197 @@
+require 'spec_helper'
+
+describe Banzai::Filter::SanitizationFilter, lib: true do
+  include FilterSpecHelper
+
+  describe 'default whitelist' do
+    it 'sanitizes tags that are not whitelisted' do
+      act = %q{<textarea>no inputs</textarea> and <blink>no blinks</blink>}
+      exp = 'no inputs and no blinks'
+      expect(filter(act).to_html).to eq exp
+    end
+
+    it 'sanitizes tag attributes' do
+      act = %q{<a href="http://example.com/bar.html" onclick="bar">Text</a>}
+      exp = %q{<a href="http://example.com/bar.html">Text</a>}
+      expect(filter(act).to_html).to eq exp
+    end
+
+    it 'sanitizes javascript in attributes' do
+      act = %q(<a href="javascript:alert('foo')">Text</a>)
+      exp = '<a>Text</a>'
+      expect(filter(act).to_html).to eq exp
+    end
+
+    it 'allows whitelisted HTML tags from the user' do
+      exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>"
+      expect(filter(act).to_html).to eq exp
+    end
+
+    it 'sanitizes `class` attribute on any element' do
+      act = %q{<strong class="foo">Strong</strong>}
+      expect(filter(act).to_html).to eq %q{<strong>Strong</strong>}
+    end
+
+    it 'sanitizes `id` attribute on any element' do
+      act = %q{<em id="foo">Emphasis</em>}
+      expect(filter(act).to_html).to eq %q{<em>Emphasis</em>}
+    end
+  end
+
+  describe 'custom whitelist' do
+    it 'customizes the whitelist only once' do
+      instance = described_class.new('Foo')
+      3.times { instance.whitelist }
+
+      expect(instance.whitelist[:transformers].size).to eq 5
+    end
+
+    it 'allows syntax highlighting' do
+      exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>}
+      expect(filter(act).to_html).to eq exp
+    end
+
+    it 'sanitizes `class` attribute from non-highlight spans' do
+      act = %q{<span class="k">def</span>}
+      expect(filter(act).to_html).to eq %q{<span>def</span>}
+    end
+
+    it 'allows `style` attribute on table elements' do
+      html = <<-HTML.strip_heredoc
+      <table>
+        <tr><th style="text-align: center">Head</th></tr>
+        <tr><td style="text-align: right">Body</th></tr>
+      </table>
+      HTML
+
+      doc = filter(html)
+
+      expect(doc.at_css('th')['style']).to eq 'text-align: center'
+      expect(doc.at_css('td')['style']).to eq 'text-align: right'
+    end
+
+    it 'allows `span` elements' do
+      exp = act = %q{<span>Hello</span>}
+      expect(filter(act).to_html).to eq exp
+    end
+
+    it 'removes `rel` attribute from `a` elements' do
+      act = %q{<a href="#" rel="nofollow">Link</a>}
+      exp = %q{<a href="#">Link</a>}
+
+      expect(filter(act).to_html).to eq exp
+    end
+
+    # Adapted from the Sanitize test suite: http://git.io/vczrM
+    protocols = {
+      'protocol-based JS injection: simple, no spaces' => {
+        input:  '<a href="javascript:alert(\'XSS\');">foo</a>',
+        output: '<a>foo</a>'
+      },
+
+      'protocol-based JS injection: simple, spaces before' => {
+        input:  '<a href="javascript    :alert(\'XSS\');">foo</a>',
+        output: '<a>foo</a>'
+      },
+
+      'protocol-based JS injection: simple, spaces after' => {
+        input:  '<a href="javascript:    alert(\'XSS\');">foo</a>',
+        output: '<a>foo</a>'
+      },
+
+      'protocol-based JS injection: simple, spaces before and after' => {
+        input:  '<a href="javascript    :   alert(\'XSS\');">foo</a>',
+        output: '<a>foo</a>'
+      },
+
+      'protocol-based JS injection: preceding colon' => {
+        input:  '<a href=":javascript:alert(\'XSS\');">foo</a>',
+        output: '<a>foo</a>'
+      },
+
+      'protocol-based JS injection: UTF-8 encoding' => {
+        input:  '<a href="javascript&#58;">foo</a>',
+        output: '<a>foo</a>'
+      },
+
+      'protocol-based JS injection: long UTF-8 encoding' => {
+        input:  '<a href="javascript&#0058;">foo</a>',
+        output: '<a>foo</a>'
+      },
+
+      'protocol-based JS injection: long UTF-8 encoding without semicolons' => {
+        input:  '<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
+        output: '<a>foo</a>'
+      },
+
+      'protocol-based JS injection: hex encoding' => {
+        input:  '<a href="javascript&#x3A;">foo</a>',
+        output: '<a>foo</a>'
+      },
+
+      'protocol-based JS injection: long hex encoding' => {
+        input:  '<a href="javascript&#x003A;">foo</a>',
+        output: '<a>foo</a>'
+      },
+
+      'protocol-based JS injection: hex encoding without semicolons' => {
+        input:  '<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>foo</a>',
+        output: '<a>foo</a>'
+      },
+
+      'protocol-based JS injection: null char' => {
+        input:  "<a href=java\0script:alert(\"XSS\")>foo</a>",
+        output: '<a href="java"></a>'
+      },
+
+      'protocol-based JS injection: spaces and entities' => {
+        input:  '<a href=" &#14;  javascript:alert(\'XSS\');">foo</a>',
+        output: '<a href="">foo</a>'
+      },
+    }
+
+    protocols.each do |name, data|
+      it "handles #{name}" do
+        doc = filter(data[:input])
+
+        expect(doc.to_html).to eq data[:output]
+      end
+    end
+
+    it 'allows non-standard anchor schemes' do
+      exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>}
+      act = filter(exp)
+
+      expect(act.to_html).to eq exp
+    end
+
+    it 'allows relative links' do
+      exp = %q{<a href="foo/bar.md">foo/bar.md</a>}
+      act = filter(exp)
+
+      expect(act.to_html).to eq exp
+    end
+  end
+
+  context 'when inline_sanitization is true' do
+    it 'uses a stricter whitelist' do
+      doc = filter('<h1>Description</h1>', inline_sanitization: true)
+      expect(doc.to_html.strip).to eq 'Description'
+    end
+
+    %w(pre code img ol ul li).each do |elem|
+      it "removes '#{elem}' elements" do
+        act = "<#{elem}>Description</#{elem}>"
+        expect(filter(act, inline_sanitization: true).to_html.strip).
+          to eq 'Description'
+      end
+    end
+
+    %w(b i strong em a ins del sup sub p).each do |elem|
+      it "still allows '#{elem}' elements" do
+        exp = act = "<#{elem}>Description</#{elem}>"
+        expect(filter(act, inline_sanitization: true).to_html).to eq exp
+      end
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..26466fbb180138b8ce28755faf51d35bc8cb0ecf
--- /dev/null
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -0,0 +1,146 @@
+require 'spec_helper'
+
+describe Banzai::Filter::SnippetReferenceFilter, lib: true do
+  include FilterSpecHelper
+
+  let(:project)   { create(:empty_project, :public) }
+  let(:snippet)   { create(:project_snippet, project: project) }
+  let(:reference) { snippet.to_reference }
+
+  it 'requires project context' do
+    expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
+  end
+
+  %w(pre code a style).each do |elem|
+    it "ignores valid references contained inside '#{elem}' element" do
+      exp = act = "<#{elem}>Snippet #{reference}</#{elem}>"
+      expect(reference_filter(act).to_html).to eq exp
+    end
+  end
+
+  context 'internal reference' do
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).to eq urls.
+        namespace_project_snippet_url(project.namespace, project, snippet)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Snippet (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid snippet IDs' do
+      exp = act = "Snippet #{invalidate_reference(reference)}"
+
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'includes a title attribute' do
+      doc = reference_filter("Snippet #{reference}")
+      expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
+    end
+
+    it 'escapes the title attribute' do
+      snippet.update_attribute(:title, %{"></a>whatever<a title="})
+
+      doc = reference_filter("Snippet #{reference}")
+      expect(doc.text).to eq "Snippet #{reference}"
+    end
+
+    it 'includes default classes' do
+      doc = reference_filter("Snippet #{reference}")
+      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
+    end
+
+    it 'includes a data-project attribute' do
+      doc = reference_filter("Snippet #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-project')
+      expect(link.attr('data-project')).to eq project.id.to_s
+    end
+
+    it 'includes a data-snippet attribute' do
+      doc = reference_filter("See #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-snippet')
+      expect(link.attr('data-snippet')).to eq snippet.id.to_s
+    end
+
+    it 'supports an :only_path context' do
+      doc = reference_filter("Snippet #{reference}", only_path: true)
+      link = doc.css('a').first.attr('href')
+
+      expect(link).not_to match %r(https?://)
+      expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Snippet #{reference}")
+      expect(result[:references][:snippet]).to eq [snippet]
+    end
+  end
+
+  context 'cross-project reference' do
+    let(:namespace) { create(:namespace, name: 'cross-reference') }
+    let(:project2)  { create(:empty_project, :public, namespace: namespace) }
+    let(:snippet)   { create(:project_snippet, project: project2) }
+    let(:reference) { snippet.to_reference(project) }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("See (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid snippet IDs on the referenced project' do
+      exp = act = "See #{invalidate_reference(reference)}"
+
+      expect(reference_filter(act).to_html).to eq exp
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Snippet #{reference}")
+      expect(result[:references][:snippet]).to eq [snippet]
+    end
+  end
+
+  context 'cross-project URL reference' do
+    let(:namespace) { create(:namespace, name: 'cross-reference') }
+    let(:project2)  { create(:empty_project, :public, namespace: namespace) }
+    let(:snippet)   { create(:project_snippet, project: project2) }
+    let(:reference) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) }
+
+    it 'links to a valid reference' do
+      doc = reference_filter("See #{reference}")
+
+      expect(doc.css('a').first.attr('href')).
+        to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("See (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/)
+    end
+
+    it 'ignores invalid snippet IDs on the referenced project' do
+      act = "See #{invalidate_reference(reference)}"
+
+      expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Snippet #{reference}")
+      expect(result[:references][:snippet]).to eq [snippet]
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..407617f3307289a8c0de9c8caeeb1fcc669db0f4
--- /dev/null
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
+  include FilterSpecHelper
+
+  it 'highlights valid code blocks' do
+    result = filter('<pre><code>def fun end</code>')
+    expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>\n")
+  end
+
+  it 'passes through invalid code blocks' do
+    allow_any_instance_of(described_class).to receive(:block_code).and_raise(StandardError)
+
+    result = filter('<pre><code>This is a test</code></pre>')
+    expect(result.to_html).to eq('<pre>This is a test</pre>')
+  end
+end
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6a5d003e87f048190f15202478b7bcb2ec9f7161
--- /dev/null
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -0,0 +1,97 @@
+# encoding: UTF-8
+
+require 'spec_helper'
+
+describe Banzai::Filter::TableOfContentsFilter, lib: true do
+  include FilterSpecHelper
+
+  def header(level, text)
+    "<h#{level}>#{text}</h#{level}>\n"
+  end
+
+  it 'does nothing when :no_header_anchors is truthy' do
+    exp = act = header(1, 'Header')
+    expect(filter(act, no_header_anchors: 1).to_html).to eq exp
+  end
+
+  it 'does nothing with empty headers' do
+    exp = act = header(1, nil)
+    expect(filter(act).to_html).to eq exp
+  end
+
+  1.upto(6) do |i|
+    it "processes h#{i} elements" do
+      html = header(i, "Header #{i}")
+      doc = filter(html)
+
+      expect(doc.css("h#{i} a").first.attr('id')).to eq "header-#{i}"
+    end
+  end
+
+  describe 'anchor tag' do
+    it 'has an `anchor` class' do
+      doc = filter(header(1, 'Header'))
+      expect(doc.css('h1 a').first.attr('class')).to eq 'anchor'
+    end
+
+    it 'links to the id' do
+      doc = filter(header(1, 'Header'))
+      expect(doc.css('h1 a').first.attr('href')).to eq '#header'
+    end
+
+    describe 'generated IDs' do
+      it 'translates spaces to dashes' do
+        doc = filter(header(1, 'This header has spaces in it'))
+        expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-has-spaces-in-it'
+      end
+
+      it 'squeezes multiple spaces and dashes' do
+        doc = filter(header(1, 'This---header     is poorly-formatted'))
+        expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-poorly-formatted'
+      end
+
+      it 'removes punctuation' do
+        doc = filter(header(1, "This, header! is, filled. with @ punctuation?"))
+        expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-filled-with-punctuation'
+      end
+
+      it 'appends a unique number to duplicates' do
+        doc = filter(header(1, 'One') + header(2, 'One'))
+
+        expect(doc.css('h1 a').first.attr('id')).to eq 'one'
+        expect(doc.css('h2 a').first.attr('id')).to eq 'one-1'
+      end
+
+      it 'supports Unicode' do
+        doc = filter(header(1, '한글'))
+        expect(doc.css('h1 a').first.attr('id')).to eq '한글'
+        expect(doc.css('h1 a').first.attr('href')).to eq '#한글'
+      end
+    end
+  end
+
+  describe 'result' do
+    def result(html)
+      HTML::Pipeline.new([described_class]).call(html)
+    end
+
+    let(:results) { result(header(1, 'Header 1') + header(2, 'Header 2')) }
+    let(:doc) { Nokogiri::XML::DocumentFragment.parse(results[:toc]) }
+
+    it 'is contained within a `ul` element' do
+      expect(doc.children.first.name).to eq 'ul'
+      expect(doc.children.first.attr('class')).to eq 'section-nav'
+    end
+
+    it 'contains an `li` element for each header' do
+      expect(doc.css('li').length).to eq 2
+
+      links = doc.css('li a')
+
+      expect(links.first.attr('href')).to eq '#header-1'
+      expect(links.first.text).to eq 'Header 1'
+      expect(links.last.attr('href')).to eq '#header-2'
+      expect(links.last.text).to eq 'Header 2'
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/task_list_filter_spec.rb b/spec/lib/banzai/filter/task_list_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f2e3a44478dc5ed0a88905e3f2b6c51c2b645793
--- /dev/null
+++ b/spec/lib/banzai/filter/task_list_filter_spec.rb
@@ -0,0 +1,10 @@
+require 'spec_helper'
+
+describe Banzai::Filter::TaskListFilter, lib: true do
+  include FilterSpecHelper
+
+  it 'does not apply `task-list` class to non-task lists' do
+    exp = act = %(<ul><li>Item</li></ul>)
+    expect(filter(act).to_html).to eq exp
+  end
+end
diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3b073a90a95739387d6a4b99583a87556e2c5a24
--- /dev/null
+++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
@@ -0,0 +1,73 @@
+# encoding: UTF-8
+
+require 'spec_helper'
+
+describe Banzai::Filter::UploadLinkFilter, lib: true do
+  def filter(doc, contexts = {})
+    contexts.reverse_merge!({
+      project: project
+    })
+
+    described_class.call(doc, contexts)
+  end
+
+  def image(path)
+    %(<img src="#{path}" />)
+  end
+
+  def link(path)
+    %(<a href="#{path}">#{path}</a>)
+  end
+
+  let(:project) { create(:project) }
+
+  shared_examples :preserve_unchanged do
+    it 'does not modify any relative URL in anchor' do
+      doc = filter(link('README.md'))
+      expect(doc.at_css('a')['href']).to eq 'README.md'
+    end
+
+    it 'does not modify any relative URL in image' do
+      doc = filter(image('files/images/logo-black.png'))
+      expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
+    end
+  end
+
+  it 'does not raise an exception on invalid URIs' do
+    act = link("://foo")
+    expect { filter(act) }.not_to raise_error
+  end
+
+  context 'with a valid repository' do
+    it 'rebuilds relative URL for a link' do
+      doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+      expect(doc.at_css('a')['href']).
+        to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+    end
+
+    it 'rebuilds relative URL for an image' do
+      doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+      expect(doc.at_css('a')['href']).
+        to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+    end
+
+    it 'does not modify absolute URL' do
+      doc = filter(link('http://example.com'))
+      expect(doc.at_css('a')['href']).to eq 'http://example.com'
+    end
+
+    it 'supports Unicode filenames' do
+      path = '/uploads/한글.png'
+      escaped = Addressable::URI.escape(path)
+
+      # Stub these methods so the file doesn't actually need to be in the repo
+      allow_any_instance_of(described_class).
+        to receive(:file_exists?).and_return(true)
+      allow_any_instance_of(described_class).
+        to receive(:image?).with(path).and_return(true)
+
+      doc = filter(image(escaped))
+      expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png"
+    end
+  end
+end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8bdebae1841bdf6782c8ff98199ffda81bf16cf6
--- /dev/null
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -0,0 +1,160 @@
+require 'spec_helper'
+
+describe Banzai::Filter::UserReferenceFilter, lib: true do
+  include FilterSpecHelper
+
+  let(:project)   { create(:empty_project, :public) }
+  let(:user)      { create(:user) }
+  let(:reference) { user.to_reference }
+
+  it 'requires project context' do
+    expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
+  end
+
+  it 'ignores invalid users' do
+    exp = act = "Hey #{invalidate_reference(reference)}"
+    expect(reference_filter(act).to_html).to eq(exp)
+  end
+
+  %w(pre code a style).each do |elem|
+    it "ignores valid references contained inside '#{elem}' element" do
+      exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
+      expect(reference_filter(act).to_html).to eq exp
+    end
+  end
+
+  context 'mentioning @all' do
+    let(:reference) { User.reference_prefix + 'all' }
+
+    before do
+      project.team << [project.creator, :developer]
+    end
+
+    it 'supports a special @all mention' do
+      doc = reference_filter("Hey #{reference}")
+      expect(doc.css('a').length).to eq 1
+      expect(doc.css('a').first.attr('href'))
+        .to eq urls.namespace_project_url(project.namespace, project)
+    end
+
+    context "when the author is a member of the project" do
+
+      it 'adds to the results hash' do
+        result = reference_pipeline_result("Hey #{reference}", author: project.creator)
+        expect(result[:references][:user]).to eq [project.creator]
+      end
+    end
+
+    context "when the author is not a member of the project" do
+
+      let(:other_user) { create(:user) }
+
+      it "doesn't add to the results hash" do
+        result = reference_pipeline_result("Hey #{reference}", author: other_user)
+        expect(result[:references][:user]).to eq []
+      end
+    end
+  end
+
+  context 'mentioning a user' do
+    it 'links to a User' do
+      doc = reference_filter("Hey #{reference}")
+      expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
+    end
+
+    it 'links to a User with a period' do
+      user = create(:user, name: 'alphA.Beta')
+
+      doc = reference_filter("Hey #{user.to_reference}")
+      expect(doc.css('a').length).to eq 1
+    end
+
+    it 'links to a User with an underscore' do
+      user = create(:user, name: 'ping_pong_king')
+
+      doc = reference_filter("Hey #{user.to_reference}")
+      expect(doc.css('a').length).to eq 1
+    end
+
+    it 'includes a data-user attribute' do
+      doc = reference_filter("Hey #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-user')
+      expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Hey #{reference}")
+      expect(result[:references][:user]).to eq [user]
+    end
+  end
+
+  context 'mentioning a group' do
+    let(:group)     { create(:group) }
+    let(:reference) { group.to_reference }
+
+    it 'links to the Group' do
+      doc = reference_filter("Hey #{reference}")
+      expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
+    end
+
+    it 'includes a data-group attribute' do
+      doc = reference_filter("Hey #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-group')
+      expect(link.attr('data-group')).to eq group.id.to_s
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Hey #{reference}")
+      expect(result[:references][:user]).to eq group.users
+    end
+  end
+
+  it 'links with adjacent text' do
+    doc = reference_filter("Mention me (#{reference}.)")
+    expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
+  end
+
+  it 'includes default classes' do
+    doc = reference_filter("Hey #{reference}")
+    expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
+  end
+
+  it 'supports an :only_path context' do
+    doc = reference_filter("Hey #{reference}", only_path: true)
+    link = doc.css('a').first.attr('href')
+
+    expect(link).not_to match %r(https?://)
+    expect(link).to eq urls.user_path(user)
+  end
+
+  context 'referencing a user in a link href' do
+    let(:reference) { %Q{<a href="#{user.to_reference}">User</a>} }
+
+    it 'links to a User' do
+      doc = reference_filter("Hey #{reference}")
+      expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
+    end
+
+    it 'links with adjacent text' do
+      doc = reference_filter("Mention me (#{reference}.)")
+      expect(doc.to_html).to match(/\(<a.+>User<\/a>\.\)/)
+    end
+
+    it 'includes a data-user attribute' do
+      doc = reference_filter("Hey #{reference}")
+      link = doc.css('a').first
+
+      expect(link).to have_attribute('data-user')
+      expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
+    end
+
+    it 'adds to the results hash' do
+      result = reference_pipeline_result("Hey #{reference}")
+      expect(result[:references][:user]).to eq [user]
+    end
+  end
+end
diff --git a/spec/lib/ci/ansi2html_spec.rb b/spec/lib/ci/ansi2html_spec.rb
index 75c023bbc43c5c2bc242582c164db1f0946563be..3a2b568f4c7ca21d2c4eef8d0e8268b9c2408645 100644
--- a/spec/lib/ci/ansi2html_spec.rb
+++ b/spec/lib/ci/ansi2html_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Ci::Ansi2html do
+describe Ci::Ansi2html, lib: true do
   subject { Ci::Ansi2html }
 
   it "prints non-ansi as-is" do
diff --git a/spec/lib/ci/charts_spec.rb b/spec/lib/ci/charts_spec.rb
index 83e2ad220b832995607ea94d86ee3a023704d386..50a77308cde81d48d3d2df677dc04964f9cb9c4c 100644
--- a/spec/lib/ci/charts_spec.rb
+++ b/spec/lib/ci/charts_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe "Charts" do
+describe Ci::Charts, lib: true do
 
   context "build_times" do
     before do
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index 6f287719ba61c84c519b566b5ede3e685a6b7fe6..d15100fc6d8c075de90677c6129d4bb0d24c8fcc 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -1,9 +1,9 @@
 require 'spec_helper'
 
 module Ci
-  describe GitlabCiYamlProcessor do
+  describe GitlabCiYamlProcessor, lib: true do
     let(:path) { 'path' }
-    
+
     describe "#builds_for_ref" do
       let(:type) { 'test' }
 
@@ -29,7 +29,7 @@ module Ci
           when: "on_success"
         })
       end
-      
+
       describe :only do
         it "does not return builds if only has another branch" do
           config = YAML.dump({
@@ -517,7 +517,7 @@ module Ci
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra")
       end
 
-      it "returns errors if there is no any jobs defined" do
+      it "returns errors if there are no jobs defined" do
         config = YAML.dump({ before_script: ["bundle update"] })
         expect do
           GitlabCiYamlProcessor.new(config, path)
@@ -532,21 +532,21 @@ module Ci
       end
 
       it "returns errors if job stage is not a string" do
-        config = YAML.dump({ rspec: { script: "test", type: 1, allow_failure: "string" } })
+        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
 
       it "returns errors if job stage is not a pre-defined stage" do
-        config = YAML.dump({ rspec: { script: "test", type: "acceptance", allow_failure: "string" } })
+        config = YAML.dump({ rspec: { script: "test", type: "acceptance" } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy")
       end
 
       it "returns errors if job stage is not a defined stage" do
-        config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", type: "acceptance", allow_failure: "string" } })
+        config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", type: "acceptance" } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test")
diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb
index a9624e9a2b76efd36b7946f083b04dcda1877786..c2a7b20b84d90b8f3ec914dd0284a256ae709785 100644
--- a/spec/lib/disable_email_interceptor_spec.rb
+++ b/spec/lib/disable_email_interceptor_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe DisableEmailInterceptor do
+describe DisableEmailInterceptor, lib: true do
   before do
     ActionMailer::Base.register_interceptor(DisableEmailInterceptor)
   end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 48bc60eed16f0d86c611c9333f83c4acb5d1e095..f38fadda9babb3099d2338bb4e33fe1bb7bafb98 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe ExtractsPath do
+describe ExtractsPath, lib: true do
   include ExtractsPath
   include RepoHelpers
   include Gitlab::Application.routes.url_helpers
diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb
index 12ccc051c74865489fece6fe855ba92182ec2b73..fda6f9a6c885294c78247fb06b33dcba3da332c1 100644
--- a/spec/lib/file_size_validator_spec.rb
+++ b/spec/lib/file_size_validator_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'Gitlab::FileSizeValidatorSpec' do
+describe FileSizeValidator, lib: true do
   let(:validator) { FileSizeValidator.new(options) }
   let(:attachment) { AttachmentUploader.new }
   let(:note) { create(:note) }
diff --git a/spec/lib/git_ref_validator_spec.rb b/spec/lib/git_ref_validator_spec.rb
index 4633b6f3934b9ddf7e00c1cf3ea35e10d3da9f57..dc57e94f193b0e5992c4f3ee0e193c5842d95be7 100644
--- a/spec/lib/git_ref_validator_spec.rb
+++ b/spec/lib/git_ref_validator_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::GitRefValidator do
+describe Gitlab::GitRefValidator, lib: true do
   it { expect(Gitlab::GitRefValidator.validate('feature/new')).to be_truthy }
   it { expect(Gitlab::GitRefValidator.validate('implement_@all')).to be_truthy }
   it { expect(Gitlab::GitRefValidator.validate('my_new_feature')).to be_truthy }
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 03e36fd35524ab596b9778cfd84c6434e40b6c6d..6beb21c6d2ba2bee1c6bf029d4169d1db8ad31e7 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
 require 'nokogiri'
 
 module Gitlab
-  describe Asciidoc do
+  describe Asciidoc, lib: true do
 
     let(:input) { '<b>ascii</b>' }
     let(:context) { {} }
@@ -50,9 +50,9 @@ module Gitlab
         filtered_html = '<b>ASCII</b>'
 
         allow(Asciidoctor).to receive(:convert).and_return(html)
-        expect_any_instance_of(HTML::Pipeline).to receive(:call)
-          .with(html, context)
-          .and_return(output: Nokogiri::HTML.fragment(filtered_html))
+        expect(Banzai).to receive(:render)
+          .with(html, context.merge(pipeline: :asciidoc))
+          .and_return(filtered_html)
 
         expect( render('foo', context) ).to eql filtered_html
       end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 72806bebe1f033e095bc6f1a5395c49712a6d1e7..aad291c03cdc33cea28019b56bce7e436b29ccde 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Auth do
+describe Gitlab::Auth, lib: true do
   let(:gl_auth) { Gitlab::Auth.new }
 
   describe :find do
diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/lib/gitlab/backend/grack_auth_spec.rb
index dfa0e10318a3ca1d01146ec3221992485a510892..cd26dca0998572c9f003c6c9e78bf7a1f6582699 100644
--- a/spec/lib/gitlab/backend/grack_auth_spec.rb
+++ b/spec/lib/gitlab/backend/grack_auth_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe Grack::Auth do
+describe Grack::Auth, lib: true do
   let(:user)    { create(:user) }
   let(:project) { create(:project) }
 
@@ -191,15 +191,10 @@ describe Grack::Auth do
 
         context "when a gitlab ci token is provided" do
           let(:token) { "123" }
-          let(:gitlab_ci_project) { FactoryGirl.create :ci_project, token: token }
+          let(:project) { FactoryGirl.create :empty_project }
 
           before do
-            project.gitlab_ci_project = gitlab_ci_project
-            project.save
-
-            gitlab_ci_service = project.build_gitlab_ci_service
-            gitlab_ci_service.active = true
-            gitlab_ci_service.save
+            project.update_attributes(runners_token: token, builds_enabled: true)
 
             env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials("gitlab-ci-token", token)
           end
diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb
index b60e23454d60f875ca722fca949fd1c228e98ccf..fd869f48b5c223890d58c5f85b86219212b986fb 100644
--- a/spec/lib/gitlab/backend/shell_spec.rb
+++ b/spec/lib/gitlab/backend/shell_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Shell do
+describe Gitlab::Shell, lib: true do
   let(:project) { double('Project', id: 7, path: 'diaspora') }
   let(:gitlab_shell) { Gitlab::Shell.new }
 
@@ -16,7 +16,7 @@ describe Gitlab::Shell do
 
   it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") }
 
-  describe Gitlab::Shell::KeyAdder do
+  describe Gitlab::Shell::KeyAdder, lib: true do
     describe '#add_key' do
       it 'normalizes space characters in the key' do
         io = spy
diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb
index dfe58637eeef8cfc994518a3512c20af2358ab95..aa0699f2ebf99634602421c3118c669d9a8d2ea1 100644
--- a/spec/lib/gitlab/bitbucket_import/client_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::BitbucketImport::Client do
+describe Gitlab::BitbucketImport::Client, lib: true do
   let(:token) { '123456' }
   let(:secret) { 'secret' }
   let(:client) { Gitlab::BitbucketImport::Client.new(token, secret) }
diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
index 0e826a319e04e382d897e558a0a7085e097a2a79..e1c60e07b4d656f9b54dd7df45eac9aa69c4d191 100644
--- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::BitbucketImport::ProjectCreator do
+describe Gitlab::BitbucketImport::ProjectCreator, lib: true do
   let(:user) { create(:user) }
   let(:repo) do
     {
diff --git a/spec/lib/gitlab/build_data_builder_spec.rb b/spec/lib/gitlab/build_data_builder_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..839b30f1ff459b4623370a56625cd1cf23c7272f
--- /dev/null
+++ b/spec/lib/gitlab/build_data_builder_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe 'Gitlab::BuildDataBuilder' do
+  let(:build) { create(:ci_build) }
+
+  describe :build do
+    let(:data) do
+      Gitlab::BuildDataBuilder.build(build)
+    end
+
+    it { expect(data).to be_a(Hash) }
+    it { expect(data[:ref]).to eq(build.ref) }
+    it { expect(data[:sha]).to eq(build.sha) }
+    it { expect(data[:tag]).to eq(build.tag) }
+    it { expect(data[:build_id]).to eq(build.id) }
+    it { expect(data[:build_status]).to eq(build.status) }
+    it { expect(data[:project_id]).to eq(build.project.id) }
+    it { expect(data[:project_name]).to eq(build.project.name_with_namespace) }
+  end
+end
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 21254f778d3f39798b1a88f276aea8f35e718926..99288da1e43f32479bfc6bc7dad7e28ea4a2681c 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -1,12 +1,19 @@
 require 'spec_helper'
 
-describe Gitlab::ClosingIssueExtractor do
+describe Gitlab::ClosingIssueExtractor, lib: true do
   let(:project)   { create(:project) }
+  let(:project2)   { create(:project) }
   let(:issue)     { create(:issue, project: project) }
+  let(:issue2)     { create(:issue, project: project2) }
   let(:reference) { issue.to_reference }
+  let(:cross_reference) { issue2.to_reference(project) }
 
   subject { described_class.new(project, project.creator) }
 
+  before do
+    project2.team << [project.creator, :master]
+  end
+
   describe "#closed_by_message" do
     context 'with a single reference' do
       it do
@@ -130,6 +137,27 @@ describe Gitlab::ClosingIssueExtractor do
       end
     end
 
+    context "with a cross-project reference" do
+      it do
+        message = "Closes #{cross_reference}"
+        expect(subject.closed_by_message(message)).to eq([issue2])
+      end
+    end
+
+    context "with a cross-project URL" do
+      it do
+        message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)}"
+        expect(subject.closed_by_message(message)).to eq([issue2])
+      end
+    end
+
+    context "with an invalid URL" do
+      it do
+        message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}"
+        expect(subject.closed_by_message(message)).to eq([])
+      end
+    end
+
     context 'with multiple references' do
       let(:other_issue) { create(:issue, project: project) }
       let(:third_issue) { create(:issue, project: project) }
@@ -171,6 +199,31 @@ describe Gitlab::ClosingIssueExtractor do
         expect(subject.closed_by_message(message)).
             to match_array([issue, other_issue, third_issue])
       end
+
+      it "fetches cross-project references" do
+        message = "Closes #{reference} and #{cross_reference}"
+
+        expect(subject.closed_by_message(message)).
+            to match_array([issue, issue2])
+      end
+
+      it "fetches cross-project URL references" do
+        message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
+
+        expect(subject.closed_by_message(message)).
+            to match_array([issue, issue2])
+      end
+
+      it "ignores invalid cross-project URL references" do
+        message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
+
+        expect(subject.closed_by_message(message)).
+            to match_array([issue])
+      end
     end
   end
+
+  def urls
+    Gitlab::Application.routes.url_helpers
+  end
 end
diff --git a/spec/lib/gitlab/color_schemes_spec.rb b/spec/lib/gitlab/color_schemes_spec.rb
index c7be45dbcd3f84368cd7de04e48a6222e7894061..0a1ec66f1999bd59f5f3c226f3167808a94fb4f6 100644
--- a/spec/lib/gitlab/color_schemes_spec.rb
+++ b/spec/lib/gitlab/color_schemes_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ColorSchemes do
+describe Gitlab::ColorSchemes, lib: true do
   describe '.body_classes' do
     it 'returns a space-separated list of class names' do
       css = described_class.body_classes
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 7cdebdf209aa540ba73e8f075491e20518167680..8461e8ce50d6c3dbf4a2cfbea877f3a6052a6a04 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Database do
+describe Gitlab::Database, lib: true do
   # These are just simple smoke tests to check if the methods work (regardless
   # of what they may return).
   describe '.mysql?' do
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 8b7946f3117269ec0edefea673971b5473f39985..c7cdf8691d61b37217bb47dd61439aa47dcb101c 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Diff::File do
+describe Gitlab::Diff::File, lib: true do
   include RepoHelpers
 
   let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index 4d5d1431683f8995c348494f8b180d77a4429a01..ba577bd28e5fa4bb1d0381376bc0a46a55341b3e 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Diff::Parser do
+describe Gitlab::Diff::Parser, lib: true do
   include RepoHelpers
 
   let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb
index 8fb432367b63ddc603ca6b128f88537c7d6b9c7b..476a21bf996c298a5b5a291a9e477c802ccf0a1c 100644
--- a/spec/lib/gitlab/email/attachment_uploader_spec.rb
+++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe Gitlab::Email::AttachmentUploader do
+describe Gitlab::Email::AttachmentUploader, lib: true do
   describe "#execute" do
     let(:project) { build(:project) }
     let(:message_raw) { fixture_file("emails/attachment.eml") }
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..56ae2a8d121d9bcdd6e6921accba0616169add6c
--- /dev/null
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -0,0 +1,122 @@
+require 'spec_helper'
+
+describe Gitlab::Email::Message::RepositoryPush do
+  include RepoHelpers
+
+  let!(:group) { create(:group, name: 'my_group') }
+  let!(:project) { create(:project, name: 'my_project', namespace: group) }
+  let!(:author) { create(:author, name: 'Author') }
+
+  let(:message) do
+    described_class.new(Notify, project.id, 'recipient@example.com', opts)
+  end
+
+  context 'new commits have been pushed to repository' do
+    let(:opts) do
+      { author_id: author.id, ref: 'master', action: :push, compare: compare,
+        send_from_committer_email: true }
+    end
+    let(:compare) do
+      Gitlab::Git::Compare.new(project.repository.raw_repository,
+                               sample_image_commit.id, sample_commit.id)
+    end
+
+    describe '#project' do
+      subject { message.project }
+      it { is_expected.to eq project }
+      it { is_expected.to be_an_instance_of Project }
+    end
+
+    describe '#project_namespace' do
+      subject { message.project_namespace }
+      it { is_expected.to eq group }
+      it { is_expected.to be_kind_of Namespace }
+    end
+
+    describe '#project_name_with_namespace' do
+      subject { message.project_name_with_namespace }
+      it { is_expected.to eq 'my_group / my_project' }
+    end
+
+    describe '#author' do
+      subject { message.author }
+      it { is_expected.to eq author }
+      it { is_expected.to be_an_instance_of User }
+    end
+
+    describe '#author_name' do
+      subject { message.author_name }
+      it { is_expected.to eq 'Author' }
+    end
+
+    describe '#commits' do
+      subject { message.commits }
+      it { is_expected.to be_kind_of Array }
+      it { is_expected.to all(be_instance_of Commit) }
+    end
+
+    describe '#diffs' do
+      subject { message.diffs }
+      it { is_expected.to all(be_an_instance_of Gitlab::Git::Diff) }
+    end
+
+    describe '#diffs_count' do
+      subject { message.diffs_count }
+      it { is_expected.to eq compare.diffs.count }
+    end
+
+    describe '#compare' do
+      subject { message.compare }
+      it { is_expected.to be_an_instance_of Gitlab::Git::Compare }
+    end
+
+    describe '#compare_timeout' do
+      subject { message.compare_timeout }
+      it { is_expected.to eq compare.timeout }
+    end
+
+    describe '#reverse_compare?' do
+      subject { message.reverse_compare? }
+      it { is_expected.to eq false }
+    end
+
+    describe '#disable_diffs?' do
+      subject { message.disable_diffs? }
+      it { is_expected.to eq false }
+    end
+
+    describe '#send_from_committer_email?' do
+      subject { message.send_from_committer_email? }
+      it { is_expected.to eq true }
+    end
+
+    describe '#action_name' do
+      subject { message.action_name }
+      it { is_expected.to eq 'pushed to' }
+    end
+
+    describe '#ref_name' do
+      subject { message.ref_name }
+      it { is_expected.to eq 'master' }
+    end
+
+    describe '#ref_type' do
+      subject { message.ref_type }
+      it { is_expected.to eq 'branch' }
+    end
+
+    describe '#target_url' do
+      subject { message.target_url }
+      it { is_expected.to include 'compare' }
+      it { is_expected.to include compare.commits.first.parents.first.id }
+      it { is_expected.to include compare.commits.last.id }
+    end
+
+    describe '#subject' do
+      subject { message.subject }
+      it { is_expected.to include "[Git][#{project.path_with_namespace}]" }
+      it { is_expected.to include "#{compare.commits.length} commits" }
+      it { is_expected.to include compare.commits.first.message.split("\n").first }
+    end
+  end
+end
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index e470b7cd5f54a2f287cc1dee9defd8b038ca9b9a..b535413bbd4bfca3784dc1e5dd7c3c59987261d4 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe Gitlab::Email::Receiver do
+describe Gitlab::Email::Receiver, lib: true do
   before do
     stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo")
   end
diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb
index 7cae1da8050053015c868791c5f1380ecdf724c3..6f8e9a4be6413c3d112dd74f3694fbbb9a6d818b 100644
--- a/spec/lib/gitlab/email/reply_parser_spec.rb
+++ b/spec/lib/gitlab/email/reply_parser_spec.rb
@@ -1,7 +1,7 @@
 require "spec_helper"
 
 # Inspired in great part by Discourse's Email::Receiver
-describe Gitlab::Email::ReplyParser do
+describe Gitlab::Email::ReplyParser, lib: true do
   describe '#execute' do
     def test_parse_body(mail_string)
       described_class.new(Mail::Message.new(mail_string)).execute
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index c7291689e320b1d75d9c0aa83043891094b4481a..9b3a0e3a75fab280ea5a94406ec79c68d6963eee 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::GitAccess do
+describe Gitlab::GitAccess, lib: true do
   let(:access) { Gitlab::GitAccess.new(actor, project) }
   let(:project) { create(:project) }
   let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 4cb91094cb3bf0bda9ff4352a3a4237e9c90d089..77ecfce6f17f01eee7f8fcc938408efaa5ae11fe 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::GitAccessWiki do
+describe Gitlab::GitAccessWiki, lib: true do
   let(:access) { Gitlab::GitAccessWiki.new(user, project) }
   let(:project) { create(:project) }
   let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb
index 26618120316ace4d5aef3ddf6eab3f5b7d546a21..49d8cdf43140db03df71ce67d7fc2332593217a4 100644
--- a/spec/lib/gitlab/github_import/client_spec.rb
+++ b/spec/lib/gitlab/github_import/client_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::GithubImport::Client do
+describe Gitlab::GithubImport::Client, lib: true do
   let(:token) { '123456' }
   let(:client) { Gitlab::GithubImport::Client.new(token) }
 
diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb
index ca61d3c5234e93902ff70c74155a935d671d6cea..c93a3ebdaeccef652ade6ce67732b54893c38e5d 100644
--- a/spec/lib/gitlab/github_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/github_import/project_creator_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::GithubImport::ProjectCreator do
+describe Gitlab::GithubImport::ProjectCreator, lib: true do
   let(:user) { create(:user) }
   let(:repo) do
     OpenStruct.new(
diff --git a/spec/lib/gitlab/gitlab_import/client_spec.rb b/spec/lib/gitlab/gitlab_import/client_spec.rb
index c511c51547409ac98519b97c4b4de59159c4d139..e6831e7c3832ba535cb0059c3c009e094cc19851 100644
--- a/spec/lib/gitlab/gitlab_import/client_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/client_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::GitlabImport::Client do
+describe Gitlab::GitlabImport::Client, lib: true do
   let(:token) { '123456' }
   let(:client) { Gitlab::GitlabImport::Client.new(token) }
 
diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
index 2d8923d14bb94309436fe65b92c49793accd2884..483f65cd0530576a0c2668d3049a856a30167bcd 100644
--- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::GitlabImport::ProjectCreator do
+describe Gitlab::GitlabImport::ProjectCreator, lib: true do
   let(:user) { create(:user) }
   let(:repo) do
     {
diff --git a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb b/spec/lib/gitlab/gitorious_import/project_creator_spec.rb
index c1125ca63574849b61f7112480ab9e45cd731a52..946712ca38eb9dfdcacfc3f1382f8e63afe3a751 100644
--- a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/gitorious_import/project_creator_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::GitoriousImport::ProjectCreator do
+describe Gitlab::GitoriousImport::ProjectCreator, lib: true do
   let(:user) { create(:user) }
   let(:repo) { Gitlab::GitoriousImport::Repository.new('foo/bar-baz-qux') }
   let(:namespace){ create(:group, owner: user) }
diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb
index 37985c062b4a06858c6911433b59df08f2f80cfd..85949ae8dc492bd5e6be44f98d48d0d2383c3462 100644
--- a/spec/lib/gitlab/google_code_import/client_spec.rb
+++ b/spec/lib/gitlab/google_code_import/client_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe Gitlab::GoogleCodeImport::Client do
+describe Gitlab::GoogleCodeImport::Client, lib: true do
   let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) }
   subject { described_class.new(raw_data) }
 
diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb
index 65ad7524cc2b6fa8339a2069e3c22c1b16de4025..647631271e0004725cf22834897ce7d15ccb6d39 100644
--- a/spec/lib/gitlab/google_code_import/importer_spec.rb
+++ b/spec/lib/gitlab/google_code_import/importer_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe Gitlab::GoogleCodeImport::Importer do
+describe Gitlab::GoogleCodeImport::Importer, lib: true do
   let(:mapped_user) { create(:user, username: "thilo123") }
   let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) }
   let(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) }
diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
index 35549b48687a2832fe7985b8ed05e9acfa860b48..499a896ee7697633d98cb60659a3fd43358ac122 100644
--- a/spec/lib/gitlab/google_code_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::GoogleCodeImport::ProjectCreator do
+describe Gitlab::GoogleCodeImport::ProjectCreator, lib: true do
   let(:user) { create(:user) }
   let(:repo) do
     Gitlab::GoogleCodeImport::Repository.new(
diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb
index 5fdb9c723b1df95b4fbbdff1a9171e939b45ecff..bcdba8d4c1220d1d0d0aa25f6def9a2f3020ca30 100644
--- a/spec/lib/gitlab/incoming_email_spec.rb
+++ b/spec/lib/gitlab/incoming_email_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe Gitlab::IncomingEmail do
+describe Gitlab::IncomingEmail, lib: true do
   describe "self.enabled?" do
     context "when reply by email is enabled" do
       before do
diff --git a/spec/lib/gitlab/inline_diff_spec.rb b/spec/lib/gitlab/inline_diff_spec.rb
index 2e0a05088ccb052f9a49286fbd04f97035db0a1b..c690c195112fd8e7fa173c060b3858c24134a0fa 100644
--- a/spec/lib/gitlab/inline_diff_spec.rb
+++ b/spec/lib/gitlab/inline_diff_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::InlineDiff do
+describe Gitlab::InlineDiff, lib: true do
   describe '#processing' do
     let(:diff) do
       <<eos
diff --git a/spec/lib/gitlab/key_fingerprint_spec.rb b/spec/lib/gitlab/key_fingerprint_spec.rb
index 266eab6e7932351a7372795cb29fdcc24f4eeefe..d09f51f3bfc2569f1f8713ee0f676ae7900bf912 100644
--- a/spec/lib/gitlab/key_fingerprint_spec.rb
+++ b/spec/lib/gitlab/key_fingerprint_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe Gitlab::KeyFingerprint do
+describe Gitlab::KeyFingerprint, lib: true do
   let(:key)         { "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" }
   let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" }
 
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index c38f212b405171114c8fe3411efde5867904036a..a628d0c01574fb3ad8371f361d46809e6565111c 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::LDAP::Access do
+describe Gitlab::LDAP::Access, lib: true do
   let(:access) { Gitlab::LDAP::Access.new user }
   let(:user) { create(:omniauth_user) }
 
@@ -13,6 +13,11 @@ describe Gitlab::LDAP::Access do
       end
 
       it { is_expected.to be_falsey }
+      
+      it 'should block user in GitLab' do
+        access.allowed?
+        expect(user).to be_blocked
+      end
     end
 
     context 'when the user is found' do
diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb
index 38076602df93d0d689d3e109007b383926f295f0..4847b5f3b0e62e724e36459ee28ffd7c04f909e0 100644
--- a/spec/lib/gitlab/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/ldap/adapter_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::LDAP::Adapter do
+describe Gitlab::LDAP::Adapter, lib: true do
   let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' }
 
   describe '#dn_matches_filter?' do
diff --git a/spec/lib/gitlab/ldap/auth_hash_spec.rb b/spec/lib/gitlab/ldap/auth_hash_spec.rb
index 7d8268536a4b2a8ed6405ab6590bcfa353772759..6a53ed1db64019ddc289cbb392a108b8a24630a3 100644
--- a/spec/lib/gitlab/ldap/auth_hash_spec.rb
+++ b/spec/lib/gitlab/ldap/auth_hash_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::LDAP::AuthHash do
+describe Gitlab::LDAP::AuthHash, lib: true do
   let(:auth_hash) do
     Gitlab::LDAP::AuthHash.new(
       OmniAuth::AuthHash.new(
diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb
index 6e3de914a45fec27aa8c0d0de21df8e0db63e155..b8f3290e84c9cd4013f25900e91130f2de31b52b 100644
--- a/spec/lib/gitlab/ldap/authentication_spec.rb
+++ b/spec/lib/gitlab/ldap/authentication_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::LDAP::Authentication do
+describe Gitlab::LDAP::Authentication, lib: true do
   let(:user)     { create(:omniauth_user, extern_uid: dn) }
   let(:dn)       { 'uid=john,ou=people,dc=example,dc=com' }
   let(:login)    { 'john' }
diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb
index 3548d647c84ae18988e21ac2ce6842d37d74ab41..835853a83a4357c30aaebf88027df0b936bf8925 100644
--- a/spec/lib/gitlab/ldap/config_spec.rb
+++ b/spec/lib/gitlab/ldap/config_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::LDAP::Config do
+describe Gitlab::LDAP::Config, lib: true do
   let(:config) { Gitlab::LDAP::Config.new provider }
   let(:provider) { 'ldapmain' }
 
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index b5b56a349526774d7f5dd98194a4e1d7092d3844..1e755259dae4a4bdb85e0f1eccd5ff8ccebefa23 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::LDAP::User do
+describe Gitlab::LDAP::User, lib: true do
   let(:ldap_user) { Gitlab::LDAP::User.new(auth_hash) }
   let(:gl_user) { ldap_user.gl_user }
   let(:info) do
@@ -42,6 +42,21 @@ describe Gitlab::LDAP::User do
     end
   end
 
+  describe '.find_by_uid_and_provider' do
+    it 'retrieves the correct user' do
+      special_info = {
+        name: 'John Åström',
+        email: 'john@example.com',
+        nickname: 'jastrom'
+      }
+      special_hash = OmniAuth::AuthHash.new(uid: 'CN=John Åström,CN=Users,DC=Example,DC=com', provider: 'ldapmain', info: special_info)
+      special_chars_user = described_class.new(special_hash)
+      user = special_chars_user.save
+
+      expect(described_class.find_by_uid_and_provider(special_hash.uid, special_hash.provider)).to eq user
+    end
+  end
+
   describe :find_or_create do
     it "finds the user if already existing" do
       create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb
index 5b13d65c7c06011e1846709fd64e16ceb3b5a412..5852b31ab3a5ff04b1002af9290acfcbac5b881d 100644
--- a/spec/lib/gitlab/lfs/lfs_router_spec.rb
+++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Lfs::Router do
+describe Gitlab::Lfs::Router, lib: true do
   let(:project) { create(:project) }
   let(:public_project) { create(:project, :public) }
   let(:forked_project) { fork_project(public_project, user) }
diff --git a/spec/lib/gitlab/markdown/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/autolink_filter_spec.rb
deleted file mode 100644
index 26332ba5217f38f18d1639557bb03fccfdb1eef0..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/autolink_filter_spec.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe AutolinkFilter do
-    include FilterSpecHelper
-
-    let(:link) { 'http://about.gitlab.com/' }
-
-    it 'does nothing when :autolink is false' do
-      exp = act = link
-      expect(filter(act, autolink: false).to_html).to eq exp
-    end
-
-    it 'does nothing with non-link text' do
-      exp = act = 'This text contains no links to autolink'
-      expect(filter(act).to_html).to eq exp
-    end
-
-    context 'Rinku schemes' do
-      it 'autolinks http' do
-        doc = filter("See #{link}")
-        expect(doc.at_css('a').text).to eq link
-        expect(doc.at_css('a')['href']).to eq link
-      end
-
-      it 'autolinks https' do
-        link = 'https://google.com/'
-        doc = filter("See #{link}")
-
-        expect(doc.at_css('a').text).to eq link
-        expect(doc.at_css('a')['href']).to eq link
-      end
-
-      it 'autolinks ftp' do
-        link = 'ftp://ftp.us.debian.org/debian/'
-        doc = filter("See #{link}")
-
-        expect(doc.at_css('a').text).to eq link
-        expect(doc.at_css('a')['href']).to eq link
-      end
-
-      it 'autolinks short URLs' do
-        link = 'http://localhost:3000/'
-        doc = filter("See #{link}")
-
-        expect(doc.at_css('a').text).to eq link
-        expect(doc.at_css('a')['href']).to eq link
-      end
-
-      it 'accepts link_attr options' do
-        doc = filter("See #{link}", link_attr: { class: 'custom' })
-
-        expect(doc.at_css('a')['class']).to eq 'custom'
-      end
-
-      described_class::IGNORE_PARENTS.each do |elem|
-        it "ignores valid links contained inside '#{elem}' element" do
-          exp = act = "<#{elem}>See #{link}</#{elem}>"
-          expect(filter(act).to_html).to eq exp
-        end
-      end
-    end
-
-    context 'other schemes' do
-      let(:link) { 'foo://bar.baz/' }
-
-      it 'autolinks smb' do
-        link = 'smb:///Volumes/shared/foo.pdf'
-        doc = filter("See #{link}")
-
-        expect(doc.at_css('a').text).to eq link
-        expect(doc.at_css('a')['href']).to eq link
-      end
-
-      it 'autolinks irc' do
-        link = 'irc://irc.freenode.net/git'
-        doc = filter("See #{link}")
-
-        expect(doc.at_css('a').text).to eq link
-        expect(doc.at_css('a')['href']).to eq link
-      end
-
-      it 'does not include trailing punctuation' do
-        doc = filter("See #{link}.")
-        expect(doc.at_css('a').text).to eq link
-
-        doc = filter("See #{link}, ok?")
-        expect(doc.at_css('a').text).to eq link
-
-        doc = filter("See #{link}...")
-        expect(doc.at_css('a').text).to eq link
-      end
-
-      it 'does not include trailing HTML entities' do
-        doc = filter("See &lt;&lt;&lt;#{link}&gt;&gt;&gt;")
-
-        expect(doc.at_css('a')['href']).to eq link
-        expect(doc.text).to eq "See <<<#{link}>>>"
-      end
-
-      it 'accepts link_attr options' do
-        doc = filter("See #{link}", link_attr: { class: 'custom' })
-        expect(doc.at_css('a')['class']).to eq 'custom'
-      end
-
-      described_class::IGNORE_PARENTS.each do |elem|
-        it "ignores valid links contained inside '#{elem}' element" do
-          exp = act = "<#{elem}>See #{link}</#{elem}>"
-          expect(filter(act).to_html).to eq exp
-        end
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
deleted file mode 100644
index e5b8d723fe5614652cc98000511f8250d554d1d7..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
+++ /dev/null
@@ -1,145 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe CommitRangeReferenceFilter do
-    include FilterSpecHelper
-
-    let(:project) { create(:project, :public) }
-    let(:commit1) { project.commit }
-    let(:commit2) { project.commit("HEAD~2") }
-
-    let(:range)  { CommitRange.new("#{commit1.id}...#{commit2.id}") }
-    let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}") }
-
-    it 'requires project context' do
-      expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
-    end
-
-    %w(pre code a style).each do |elem|
-      it "ignores valid references contained inside '#{elem}' element" do
-        exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
-      end
-    end
-
-    context 'internal reference' do
-      let(:reference)  { range.to_reference }
-      let(:reference2) { range2.to_reference }
-
-      it 'links to a valid two-dot reference' do
-        doc = filter("See #{reference2}")
-
-        expect(doc.css('a').first.attr('href')).
-          to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
-      end
-
-      it 'links to a valid three-dot reference' do
-        doc = filter("See #{reference}")
-
-        expect(doc.css('a').first.attr('href')).
-          to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
-      end
-
-      it 'links to a valid short ID' do
-        reference = "#{commit1.short_id}...#{commit2.id}"
-        reference2 = "#{commit1.id}...#{commit2.short_id}"
-
-        exp = commit1.short_id + '...' + commit2.short_id
-
-        expect(filter("See #{reference}").css('a').first.text).to eq exp
-        expect(filter("See #{reference2}").css('a').first.text).to eq exp
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("See (#{reference}.)")
-
-        exp = Regexp.escape(range.to_s)
-        expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
-      end
-
-      it 'ignores invalid commit IDs' do
-        exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
-
-        expect(project).to receive(:valid_repo?).and_return(true)
-        expect(project.repository).to receive(:commit).with(commit1.id.reverse)
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'includes a title attribute' do
-        doc = filter("See #{reference}")
-        expect(doc.css('a').first.attr('title')).to eq range.reference_title
-      end
-
-      it 'includes default classes' do
-        doc = filter("See #{reference}")
-        expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
-      end
-
-      it 'includes a data-project attribute' do
-        doc = filter("See #{reference}")
-        link = doc.css('a').first
-
-        expect(link).to have_attribute('data-project')
-        expect(link.attr('data-project')).to eq project.id.to_s
-      end
-
-      it 'includes a data-commit-range attribute' do
-        doc = filter("See #{reference}")
-        link = doc.css('a').first
-
-        expect(link).to have_attribute('data-commit-range')
-        expect(link.attr('data-commit-range')).to eq range.to_reference
-      end
-
-      it 'supports an :only_path option' do
-        doc = filter("See #{reference}", only_path: true)
-        link = doc.css('a').first.attr('href')
-
-        expect(link).not_to match %r(https?://)
-        expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true)
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("See #{reference}")
-        expect(result[:references][:commit_range]).not_to be_empty
-      end
-    end
-
-    context 'cross-project reference' do
-      let(:namespace) { create(:namespace, name: 'cross-reference') }
-      let(:project2)  { create(:project, :public, namespace: namespace) }
-      let(:reference) { range.to_reference(project) }
-
-      before do
-        range.project = project2
-      end
-
-      it 'links to a valid reference' do
-        doc = filter("See #{reference}")
-
-        expect(doc.css('a').first.attr('href')).
-          to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("Fixed (#{reference}.)")
-
-        exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
-        expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
-      end
-
-      it 'ignores invalid commit IDs on the referenced project' do
-        exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
-        expect(filter(act).to_html).to eq exp
-
-        exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("See #{reference}")
-        expect(result[:references][:commit_range]).not_to be_empty
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
deleted file mode 100644
index d080efbf3d4fa011a7cea580601322c7120c2189..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe CommitReferenceFilter do
-    include FilterSpecHelper
-
-    let(:project) { create(:project, :public) }
-    let(:commit)  { project.commit }
-
-    it 'requires project context' do
-      expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
-    end
-
-    %w(pre code a style).each do |elem|
-      it "ignores valid references contained inside '#{elem}' element" do
-        exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
-      end
-    end
-
-    context 'internal reference' do
-      let(:reference) { commit.id }
-
-      # Let's test a variety of commit SHA sizes just to be paranoid
-      [6, 8, 12, 18, 20, 32, 40].each do |size|
-        it "links to a valid reference of #{size} characters" do
-          doc = filter("See #{reference[0...size]}")
-
-          expect(doc.css('a').first.text).to eq commit.short_id
-          expect(doc.css('a').first.attr('href')).
-            to eq urls.namespace_project_commit_url(project.namespace, project, reference)
-        end
-      end
-
-      it 'always uses the short ID as the link text' do
-        doc = filter("See #{commit.id}")
-        expect(doc.text).to eq "See #{commit.short_id}"
-
-        doc = filter("See #{commit.id[0...6]}")
-        expect(doc.text).to eq "See #{commit.short_id}"
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("See (#{reference}.)")
-        expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
-      end
-
-      it 'ignores invalid commit IDs' do
-        invalid = invalidate_reference(reference)
-        exp = act = "See #{invalid}"
-
-        expect(project).to receive(:valid_repo?).and_return(true)
-        expect(project.repository).to receive(:commit).with(invalid)
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'includes a title attribute' do
-        doc = filter("See #{reference}")
-        expect(doc.css('a').first.attr('title')).to eq commit.link_title
-      end
-
-      it 'escapes the title attribute' do
-        allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="})
-
-        doc = filter("See #{reference}")
-        expect(doc.text).to eq "See #{commit.short_id}"
-      end
-
-      it 'includes default classes' do
-        doc = filter("See #{reference}")
-        expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
-      end
-
-      it 'includes a data-project attribute' do
-        doc = filter("See #{reference}")
-        link = doc.css('a').first
-
-        expect(link).to have_attribute('data-project')
-        expect(link.attr('data-project')).to eq project.id.to_s
-      end
-
-      it 'includes a data-commit attribute' do
-        doc = filter("See #{reference}")
-        link = doc.css('a').first
-
-        expect(link).to have_attribute('data-commit')
-        expect(link.attr('data-commit')).to eq commit.id
-      end
-
-      it 'supports an :only_path context' do
-        doc = filter("See #{reference}", only_path: true)
-        link = doc.css('a').first.attr('href')
-
-        expect(link).not_to match %r(https?://)
-        expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true)
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("See #{reference}")
-        expect(result[:references][:commit]).not_to be_empty
-      end
-    end
-
-    context 'cross-project reference' do
-      let(:namespace) { create(:namespace, name: 'cross-reference') }
-      let(:project2)  { create(:project, :public, namespace: namespace) }
-      let(:commit)    { project2.commit }
-      let(:reference) { commit.to_reference(project) }
-
-      it 'links to a valid reference' do
-        doc = filter("See #{reference}")
-
-        expect(doc.css('a').first.attr('href')).
-          to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("Fixed (#{reference}.)")
-
-        exp = Regexp.escape(project2.to_reference)
-        expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
-      end
-
-      it 'ignores invalid commit IDs on the referenced project' do
-        exp = act = "Committed #{invalidate_reference(reference)}"
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("See #{reference}")
-        expect(result[:references][:commit]).not_to be_empty
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
deleted file mode 100644
index 8d4f9e403a603bfe37c1f6d22fa3df75f0e13dcf..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe CrossProjectReference do
-    include described_class
-
-    describe '#project_from_ref' do
-      context 'when no project was referenced' do
-        it 'returns the project from context' do
-          project = double
-
-          allow(self).to receive(:context).and_return({ project: project })
-
-          expect(project_from_ref(nil)).to eq project
-        end
-      end
-
-      context 'when referenced project does not exist' do
-        it 'returns nil' do
-          expect(project_from_ref('invalid/reference')).to be_nil
-        end
-      end
-
-      context 'when referenced project exists' do
-        it 'returns the referenced project' do
-          project2 = double('referenced project')
-
-          expect(Project).to receive(:find_with_namespace).
-            with('cross/reference').and_return(project2)
-
-          expect(project_from_ref('cross/reference')).to eq project2
-        end
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
deleted file mode 100644
index 11efd9bb4cd33f36205fa7d7d02d7d018ff3f42e..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/emoji_filter_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe EmojiFilter do
-    include FilterSpecHelper
-
-    before do
-      ActionController::Base.asset_host = 'https://foo.com'
-    end
-
-    it 'replaces supported emoji' do
-      doc = filter('<p>:heart:</p>')
-      expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png'
-    end
-
-    it 'ignores unsupported emoji' do
-      exp = act = '<p>:foo:</p>'
-      doc = filter(act)
-      expect(doc.to_html).to match Regexp.escape(exp)
-    end
-
-    it 'correctly encodes the URL' do
-      doc = filter('<p>:+1:</p>')
-      expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png'
-    end
-
-    it 'matches at the start of a string' do
-      doc = filter(':+1:')
-      expect(doc.css('img').size).to eq 1
-    end
-
-    it 'matches at the end of a string' do
-      doc = filter('This gets a :-1:')
-      expect(doc.css('img').size).to eq 1
-    end
-
-    it 'matches with adjacent text' do
-      doc = filter('+1 (:+1:)')
-      expect(doc.css('img').size).to eq 1
-    end
-
-    it 'matches multiple emoji in a row' do
-      doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
-      expect(doc.css('img').size).to eq 3
-    end
-
-    it 'has a title attribute' do
-      doc = filter(':-1:')
-      expect(doc.css('img').first.attr('title')).to eq ':-1:'
-    end
-
-    it 'has an alt attribute' do
-      doc = filter(':-1:')
-      expect(doc.css('img').first.attr('alt')).to eq ':-1:'
-    end
-
-    it 'has an align attribute' do
-      doc = filter(':8ball:')
-      expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
-    end
-
-    it 'has an emoji class' do
-      doc = filter(':cat:')
-      expect(doc.css('img').first.attr('class')).to eq 'emoji'
-    end
-
-    it 'has height and width attributes' do
-      doc = filter(':dog:')
-      img = doc.css('img').first
-
-      expect(img.attr('width')).to eq '20'
-      expect(img.attr('height')).to eq '20'
-    end
-
-    it 'keeps whitespace intact' do
-      doc = filter('This deserves a :+1:, big time.')
-
-      expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
-    end
-
-    it 'uses a custom asset_root context' do
-      root = Gitlab.config.gitlab.url + 'gitlab/root'
-
-      doc = filter(':smile:', asset_root: root)
-      expect(doc.css('img').first.attr('src')).to start_with(root)
-    end
-
-    it 'uses a custom asset_host context' do
-      ActionController::Base.asset_host = 'https://cdn.example.com'
-
-      doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?')
-      expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
deleted file mode 100644
index d8c2970b6bdc9a7a31b2b471b740b1f5c88a5cb0..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe ExternalIssueReferenceFilter do
-    include FilterSpecHelper
-
-    def helper
-      IssuesHelper
-    end
-
-    let(:project) { create(:jira_project) }
-
-    context 'JIRA issue references' do
-      let(:issue)     { ExternalIssue.new('JIRA-123', project) }
-      let(:reference) { issue.to_reference }
-
-      it 'requires project context' do
-        expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
-      end
-
-      %w(pre code a style).each do |elem|
-        it "ignores valid references contained inside '#{elem}' element" do
-          exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
-          expect(filter(act).to_html).to eq exp
-        end
-      end
-
-      it 'ignores valid references when using default tracker' do
-        expect(project).to receive(:default_issues_tracker?).and_return(true)
-
-        exp = act = "Issue #{reference}"
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'links to a valid reference' do
-        doc = filter("Issue #{reference}")
-        expect(doc.css('a').first.attr('href'))
-          .to eq helper.url_for_issue(reference, project)
-      end
-
-      it 'links to the external tracker' do
-        doc = filter("Issue #{reference}")
-        link = doc.css('a').first.attr('href')
-
-        expect(link).to eq "http://jira.example/browse/#{reference}"
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("Issue (#{reference}.)")
-        expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
-      end
-
-      it 'includes a title attribute' do
-        doc = filter("Issue #{reference}")
-        expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker"
-      end
-
-      it 'escapes the title attribute' do
-        allow(project.external_issue_tracker).to receive(:title).
-          and_return(%{"></a>whatever<a title="})
-
-        doc = filter("Issue #{reference}")
-        expect(doc.text).to eq "Issue #{reference}"
-      end
-
-      it 'includes default classes' do
-        doc = filter("Issue #{reference}")
-        expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
-      end
-
-      it 'supports an :only_path context' do
-        doc = filter("Issue #{reference}", only_path: true)
-        link = doc.css('a').first.attr('href')
-
-        expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true)
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/external_link_filter_spec.rb b/spec/lib/gitlab/markdown/external_link_filter_spec.rb
deleted file mode 100644
index a040b34577b958c033bfa8fc14d6d14877192937..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/external_link_filter_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe ExternalLinkFilter do
-    include FilterSpecHelper
-
-    it 'ignores elements without an href attribute' do
-      exp = act = %q(<a id="ignored">Ignore Me</a>)
-      expect(filter(act).to_html).to eq exp
-    end
-
-    it 'ignores non-HTTP(S) links' do
-      exp = act = %q(<a href="irc://irc.freenode.net/gitlab">IRC</a>)
-      expect(filter(act).to_html).to eq exp
-    end
-
-    it 'skips internal links' do
-      internal = Gitlab.config.gitlab.url
-      exp = act = %Q(<a href="#{internal}/sign_in">Login</a>)
-      expect(filter(act).to_html).to eq exp
-    end
-
-    it 'adds rel="nofollow" to external links' do
-      act = %q(<a href="https://google.com/">Google</a>)
-      doc = filter(act)
-
-      expect(doc.at_css('a')).to have_attribute('rel')
-      expect(doc.at_css('a')['rel']).to eq 'nofollow'
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
deleted file mode 100644
index 94c80ae6611aeb2221ecabc6b57d6971de02887c..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
+++ /dev/null
@@ -1,139 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe IssueReferenceFilter do
-    include FilterSpecHelper
-
-    def helper
-      IssuesHelper
-    end
-
-    let(:project) { create(:empty_project, :public) }
-    let(:issue)   { create(:issue, project: project) }
-
-    it 'requires project context' do
-      expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
-    end
-
-    %w(pre code a style).each do |elem|
-      it "ignores valid references contained inside '#{elem}' element" do
-        exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
-      end
-    end
-
-    context 'internal reference' do
-      let(:reference) { issue.to_reference }
-
-      it 'ignores valid references when using non-default tracker' do
-        expect(project).to receive(:get_issue).with(issue.iid).and_return(nil)
-
-        exp = act = "Issue #{reference}"
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'links to a valid reference' do
-        doc = filter("Fixed #{reference}")
-
-        expect(doc.css('a').first.attr('href')).
-          to eq helper.url_for_issue(issue.iid, project)
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("Fixed (#{reference}.)")
-        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
-      end
-
-      it 'ignores invalid issue IDs' do
-        invalid = invalidate_reference(reference)
-        exp = act = "Fixed #{invalid}"
-
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'includes a title attribute' do
-        doc = filter("Issue #{reference}")
-        expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
-      end
-
-      it 'escapes the title attribute' do
-        issue.update_attribute(:title, %{"></a>whatever<a title="})
-
-        doc = filter("Issue #{reference}")
-        expect(doc.text).to eq "Issue #{reference}"
-      end
-
-      it 'includes default classes' do
-        doc = filter("Issue #{reference}")
-        expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
-      end
-
-      it 'includes a data-project attribute' do
-        doc = filter("Issue #{reference}")
-        link = doc.css('a').first
-
-        expect(link).to have_attribute('data-project')
-        expect(link.attr('data-project')).to eq project.id.to_s
-      end
-
-      it 'includes a data-issue attribute' do
-        doc = filter("See #{reference}")
-        link = doc.css('a').first
-
-        expect(link).to have_attribute('data-issue')
-        expect(link.attr('data-issue')).to eq issue.id.to_s
-      end
-
-      it 'supports an :only_path context' do
-        doc = filter("Issue #{reference}", only_path: true)
-        link = doc.css('a').first.attr('href')
-
-        expect(link).not_to match %r(https?://)
-        expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true)
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("Fixed #{reference}")
-        expect(result[:references][:issue]).to eq [issue]
-      end
-    end
-
-    context 'cross-project reference' do
-      let(:namespace) { create(:namespace, name: 'cross-reference') }
-      let(:project2)  { create(:empty_project, :public, namespace: namespace) }
-      let(:issue)     { create(:issue, project: project2) }
-      let(:reference) { issue.to_reference(project) }
-
-      it 'ignores valid references when cross-reference project uses external tracker' do
-        expect_any_instance_of(Project).to receive(:get_issue).
-          with(issue.iid).and_return(nil)
-
-        exp = act = "Issue #{reference}"
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'links to a valid reference' do
-        doc = filter("See #{reference}")
-
-        expect(doc.css('a').first.attr('href')).
-          to eq helper.url_for_issue(issue.iid, project2)
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("Fixed (#{reference}.)")
-        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
-      end
-
-      it 'ignores invalid issue IDs on the referenced project' do
-        exp = act = "Fixed #{invalidate_reference(reference)}"
-
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("Fixed #{reference}")
-        expect(result[:references][:issue]).to eq [issue]
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
deleted file mode 100644
index fc21b65a8437106d35f54ac484d0f4bdbda86cf8..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
+++ /dev/null
@@ -1,144 +0,0 @@
-require 'spec_helper'
-require 'html/pipeline'
-
-module Gitlab::Markdown
-  describe LabelReferenceFilter do
-    include FilterSpecHelper
-
-    let(:project)   { create(:empty_project, :public) }
-    let(:label)     { create(:label, project: project) }
-    let(:reference) { label.to_reference }
-
-    it 'requires project context' do
-      expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
-    end
-
-    %w(pre code a style).each do |elem|
-      it "ignores valid references contained inside '#{elem}' element" do
-        exp = act = "<#{elem}>Label #{reference}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
-      end
-    end
-
-    it 'includes default classes' do
-      doc = filter("Label #{reference}")
-      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
-    end
-
-    it 'includes a data-project attribute' do
-      doc = filter("Label #{reference}")
-      link = doc.css('a').first
-
-      expect(link).to have_attribute('data-project')
-      expect(link.attr('data-project')).to eq project.id.to_s
-    end
-
-    it 'includes a data-label attribute' do
-      doc = filter("See #{reference}")
-      link = doc.css('a').first
-
-      expect(link).to have_attribute('data-label')
-      expect(link.attr('data-label')).to eq label.id.to_s
-    end
-
-    it 'supports an :only_path context' do
-      doc = filter("Label #{reference}", only_path: true)
-      link = doc.css('a').first.attr('href')
-
-      expect(link).not_to match %r(https?://)
-      expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name)
-    end
-
-    it 'adds to the results hash' do
-      result = reference_pipeline_result("Label #{reference}")
-      expect(result[:references][:label]).to eq [label]
-    end
-
-    describe 'label span element' do
-      it 'includes default classes' do
-        doc = filter("Label #{reference}")
-        expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
-      end
-
-      it 'includes a style attribute' do
-        doc = filter("Label #{reference}")
-        expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/)
-      end
-    end
-
-    context 'Integer-based references' do
-      it 'links to a valid reference' do
-        doc = filter("See #{reference}")
-
-        expect(doc.css('a').first.attr('href')).to eq urls.
-          namespace_project_issues_url(project.namespace, project, label_name: label.name)
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("Label (#{reference}.)")
-        expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
-      end
-
-      it 'ignores invalid label IDs' do
-        exp = act = "Label #{invalidate_reference(reference)}"
-
-        expect(filter(act).to_html).to eq exp
-      end
-    end
-
-    context 'String-based single-word references' do
-      let(:label)     { create(:label, name: 'gfm', project: project) }
-      let(:reference) { "#{Label.reference_prefix}#{label.name}" }
-
-      it 'links to a valid reference' do
-        doc = filter("See #{reference}")
-
-        expect(doc.css('a').first.attr('href')).to eq urls.
-          namespace_project_issues_url(project.namespace, project, label_name: label.name)
-        expect(doc.text).to eq 'See gfm'
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("Label (#{reference}.)")
-        expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
-      end
-
-      it 'ignores invalid label names' do
-        exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}"
-
-        expect(filter(act).to_html).to eq exp
-      end
-    end
-
-    context 'String-based multi-word references in quotes' do
-      let(:label)     { create(:label, name: 'gfm references', project: project) }
-      let(:reference) { label.to_reference(:name) }
-
-      it 'links to a valid reference' do
-        doc = filter("See #{reference}")
-
-        expect(doc.css('a').first.attr('href')).to eq urls.
-          namespace_project_issues_url(project.namespace, project, label_name: label.name)
-        expect(doc.text).to eq 'See gfm references'
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("Label (#{reference}.)")
-        expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
-      end
-
-      it 'ignores invalid label names' do
-        exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
-
-        expect(filter(act).to_html).to eq exp
-      end
-    end
-
-    describe 'edge cases' do
-      it 'gracefully handles non-references matching the pattern' do
-        exp = act = '(format nil "~0f" 3.0) ; 3.0'
-        expect(filter(act).to_html).to eq exp
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
deleted file mode 100644
index 3ef6cdfff33b11ca5f39a87c5f069608c497b904..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe MergeRequestReferenceFilter do
-    include FilterSpecHelper
-
-    let(:project) { create(:project, :public) }
-    let(:merge)   { create(:merge_request, source_project: project) }
-
-    it 'requires project context' do
-      expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
-    end
-
-    %w(pre code a style).each do |elem|
-      it "ignores valid references contained inside '#{elem}' element" do
-        exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
-      end
-    end
-
-    context 'internal reference' do
-      let(:reference) { merge.to_reference }
-
-      it 'links to a valid reference' do
-        doc = filter("See #{reference}")
-
-        expect(doc.css('a').first.attr('href')).to eq urls.
-          namespace_project_merge_request_url(project.namespace, project, merge)
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("Merge (#{reference}.)")
-        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
-      end
-
-      it 'ignores invalid merge IDs' do
-        exp = act = "Merge #{invalidate_reference(reference)}"
-
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'includes a title attribute' do
-        doc = filter("Merge #{reference}")
-        expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
-      end
-
-      it 'escapes the title attribute' do
-        merge.update_attribute(:title, %{"></a>whatever<a title="})
-
-        doc = filter("Merge #{reference}")
-        expect(doc.text).to eq "Merge #{reference}"
-      end
-
-      it 'includes default classes' do
-        doc = filter("Merge #{reference}")
-        expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
-      end
-
-      it 'includes a data-project attribute' do
-        doc = filter("Merge #{reference}")
-        link = doc.css('a').first
-
-        expect(link).to have_attribute('data-project')
-        expect(link.attr('data-project')).to eq project.id.to_s
-      end
-
-      it 'includes a data-merge-request attribute' do
-        doc = filter("See #{reference}")
-        link = doc.css('a').first
-
-        expect(link).to have_attribute('data-merge-request')
-        expect(link.attr('data-merge-request')).to eq merge.id.to_s
-      end
-
-      it 'supports an :only_path context' do
-        doc = filter("Merge #{reference}", only_path: true)
-        link = doc.css('a').first.attr('href')
-
-        expect(link).not_to match %r(https?://)
-        expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true)
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("Merge #{reference}")
-        expect(result[:references][:merge_request]).to eq [merge]
-      end
-    end
-
-    context 'cross-project reference' do
-      let(:namespace) { create(:namespace, name: 'cross-reference') }
-      let(:project2)  { create(:project, :public, namespace: namespace) }
-      let(:merge)     { create(:merge_request, source_project: project2) }
-      let(:reference) { merge.to_reference(project) }
-
-      it 'links to a valid reference' do
-        doc = filter("See #{reference}")
-
-        expect(doc.css('a').first.attr('href')).
-          to eq urls.namespace_project_merge_request_url(project2.namespace,
-                                                        project, merge)
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("Merge (#{reference}.)")
-        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
-      end
-
-      it 'ignores invalid merge IDs on the referenced project' do
-        exp = act = "Merge #{invalidate_reference(reference)}"
-
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("Merge #{reference}")
-        expect(result[:references][:merge_request]).to eq [merge]
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/redactor_filter_spec.rb b/spec/lib/gitlab/markdown/redactor_filter_spec.rb
deleted file mode 100644
index eea3f1cf370920b21d578a016a2a21fe5f3feea2..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/redactor_filter_spec.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe RedactorFilter do
-    include ActionView::Helpers::UrlHelper
-    include FilterSpecHelper
-
-    it 'ignores non-GFM links' do
-      html = %(See <a href="https://google.com/">Google</a>)
-      doc = filter(html, current_user: double)
-
-      expect(doc.css('a').length).to eq 1
-    end
-
-    def reference_link(data)
-      link_to('text', '', class: 'gfm', data: data)
-    end
-
-    context 'with data-project' do
-      it 'removes unpermitted Project references' do
-        user = create(:user)
-        project = create(:empty_project)
-
-        link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
-        doc = filter(link, current_user: user)
-
-        expect(doc.css('a').length).to eq 0
-      end
-
-      it 'allows permitted Project references' do
-        user = create(:user)
-        project = create(:empty_project)
-        project.team << [user, :master]
-
-        link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
-        doc = filter(link, current_user: user)
-
-        expect(doc.css('a').length).to eq 1
-      end
-
-      it 'handles invalid Project references' do
-        link = reference_link(project: 12345, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
-
-        expect { filter(link) }.not_to raise_error
-      end
-    end
-
-    context "for user references" do
-
-      context 'with data-group' do
-        it 'removes unpermitted Group references' do
-          user = create(:user)
-          group = create(:group)
-
-          link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
-          doc = filter(link, current_user: user)
-
-          expect(doc.css('a').length).to eq 0
-        end
-
-        it 'allows permitted Group references' do
-          user = create(:user)
-          group = create(:group)
-          group.add_developer(user)
-
-          link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
-          doc = filter(link, current_user: user)
-
-          expect(doc.css('a').length).to eq 1
-        end
-
-        it 'handles invalid Group references' do
-          link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
-
-          expect { filter(link) }.not_to raise_error
-        end
-      end
-
-      context 'with data-user' do
-        it 'allows any User reference' do
-          user = create(:user)
-
-          link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
-          doc = filter(link)
-
-          expect(doc.css('a').length).to eq 1
-        end
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb b/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb
deleted file mode 100644
index 4fa473ad191a0daad3bf517add4d43f16a974e73..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe ReferenceGathererFilter do
-    include ActionView::Helpers::UrlHelper
-    include FilterSpecHelper
-
-    def reference_link(data)
-      link_to('text', '', class: 'gfm', data: data)
-    end
-
-    context "for issue references" do
-
-      context 'with data-project' do
-        it 'removes unpermitted Project references' do
-          user = create(:user)
-          project = create(:empty_project)
-          issue = create(:issue, project: project)
-
-          link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
-          result = pipeline_result(link, current_user: user)
-
-          expect(result[:references][:issue]).to be_empty
-        end
-
-        it 'allows permitted Project references' do
-          user = create(:user)
-          project = create(:empty_project)
-          issue = create(:issue, project: project)
-          project.team << [user, :master]
-
-          link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
-          result = pipeline_result(link, current_user: user)
-
-          expect(result[:references][:issue]).to eq([issue])
-        end
-
-        it 'handles invalid Project references' do
-          link = reference_link(project: 12345, issue: 12345, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
-
-          expect { pipeline_result(link) }.not_to raise_error
-        end
-      end
-    end
-
-    context "for user references" do
-
-      context 'with data-group' do
-        it 'removes unpermitted Group references' do
-          user = create(:user)
-          group = create(:group)
-
-          link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
-          result = pipeline_result(link, current_user: user)
-
-          expect(result[:references][:user]).to be_empty
-        end
-
-        it 'allows permitted Group references' do
-          user = create(:user)
-          group = create(:group)
-          group.add_developer(user)
-
-          link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
-          result = pipeline_result(link, current_user: user)
-
-          expect(result[:references][:user]).to eq([user])
-        end
-
-        it 'handles invalid Group references' do
-          link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
-
-          expect { pipeline_result(link) }.not_to raise_error
-        end
-      end
-
-      context 'with data-user' do
-        it 'allows any User reference' do
-          user = create(:user)
-
-          link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
-          result = pipeline_result(link)
-
-          expect(result[:references][:user]).to eq([user])
-        end
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/relative_link_filter_spec.rb b/spec/lib/gitlab/markdown/relative_link_filter_spec.rb
deleted file mode 100644
index 027336ceb73026f65a0fe4951e751db486693d2b..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/relative_link_filter_spec.rb
+++ /dev/null
@@ -1,149 +0,0 @@
-# encoding: UTF-8
-
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe RelativeLinkFilter do
-    def filter(doc, contexts = {})
-      contexts.reverse_merge!({
-        commit:         project.commit,
-        project:        project,
-        project_wiki:   project_wiki,
-        ref:            ref,
-        requested_path: requested_path
-      })
-
-      described_class.call(doc, contexts)
-    end
-
-    def image(path)
-      %(<img src="#{path}" />)
-    end
-
-    def link(path)
-      %(<a href="#{path}">#{path}</a>)
-    end
-
-    let(:project)        { create(:project) }
-    let(:project_path)   { project.path_with_namespace }
-    let(:ref)            { 'markdown' }
-    let(:project_wiki)   { nil }
-    let(:requested_path) { '/' }
-
-    shared_examples :preserve_unchanged do
-      it 'does not modify any relative URL in anchor' do
-        doc = filter(link('README.md'))
-        expect(doc.at_css('a')['href']).to eq 'README.md'
-      end
-
-      it 'does not modify any relative URL in image' do
-        doc = filter(image('files/images/logo-black.png'))
-        expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
-      end
-    end
-
-    shared_examples :relative_to_requested do
-      it 'rebuilds URL relative to the requested path' do
-        doc = filter(link('users.md'))
-        expect(doc.at_css('a')['href']).
-          to eq "/#{project_path}/blob/#{ref}/doc/api/users.md"
-      end
-    end
-
-    context 'with a project_wiki' do
-      let(:project_wiki) { double('ProjectWiki') }
-      include_examples :preserve_unchanged
-    end
-
-    context 'without a repository' do
-      let(:project) { create(:empty_project) }
-      include_examples :preserve_unchanged
-    end
-
-    context 'with an empty repository' do
-      let(:project) { create(:project_empty_repo) }
-      include_examples :preserve_unchanged
-    end
-
-    it 'does not raise an exception on invalid URIs' do
-      act = link("://foo")
-      expect { filter(act) }.not_to raise_error
-    end
-
-    context 'with a valid repository' do
-      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 up one directory' do
-        relative_link = link('../api/README.md')
-        doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.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 multiple directories' do
-        relative_link = link('../../../api/README.md')
-        doc = filter(relative_link, requested_path: 'doc/foo/bar/baz/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 an anchor' do
-        doc = filter(link('README.md#section'))
-        expect(doc.at_css('a')['href']).
-          to eq "/#{project_path}/blob/#{ref}/README.md#section"
-      end
-
-      it 'rebuilds relative URL for a directory in the repo' do
-        doc = filter(link('doc/api/'))
-        expect(doc.at_css('a')['href']).
-          to eq "/#{project_path}/tree/#{ref}/doc/api"
-      end
-
-      it 'rebuilds relative URL for 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 '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'
-      end
-
-      it 'does not modify absolute URL' do
-        doc = filter(link('http://example.com'))
-        expect(doc.at_css('a')['href']).to eq 'http://example.com'
-      end
-
-      it 'supports Unicode filenames' do
-        path = 'files/images/한글.png'
-        escaped = Addressable::URI.escape(path)
-
-        # Stub these methods so the file doesn't actually need to be in the repo
-        allow_any_instance_of(described_class).
-          to receive(:file_exists?).and_return(true)
-        allow_any_instance_of(described_class).
-          to receive(:image?).with(path).and_return(true)
-
-        doc = filter(image(escaped))
-        expect(doc.at_css('img')['src']).to match '/raw/'
-      end
-
-      context 'when requested path is a file in the repo' do
-        let(:requested_path) { 'doc/api/README.md' }
-        include_examples :relative_to_requested
-      end
-
-      context 'when requested path is a directory in the repo' do
-        let(:requested_path) { 'doc/api' }
-        include_examples :relative_to_requested
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
deleted file mode 100644
index 27cd00e80542a7da10c6e9394aafaffe7a3f863e..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
+++ /dev/null
@@ -1,199 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe SanitizationFilter do
-    include FilterSpecHelper
-
-    describe 'default whitelist' do
-      it 'sanitizes tags that are not whitelisted' do
-        act = %q{<textarea>no inputs</textarea> and <blink>no blinks</blink>}
-        exp = 'no inputs and no blinks'
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'sanitizes tag attributes' do
-        act = %q{<a href="http://example.com/bar.html" onclick="bar">Text</a>}
-        exp = %q{<a href="http://example.com/bar.html">Text</a>}
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'sanitizes javascript in attributes' do
-        act = %q(<a href="javascript:alert('foo')">Text</a>)
-        exp = '<a>Text</a>'
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'allows whitelisted HTML tags from the user' do
-        exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>"
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'sanitizes `class` attribute on any element' do
-        act = %q{<strong class="foo">Strong</strong>}
-        expect(filter(act).to_html).to eq %q{<strong>Strong</strong>}
-      end
-
-      it 'sanitizes `id` attribute on any element' do
-        act = %q{<em id="foo">Emphasis</em>}
-        expect(filter(act).to_html).to eq %q{<em>Emphasis</em>}
-      end
-    end
-
-    describe 'custom whitelist' do
-      it 'customizes the whitelist only once' do
-        instance = described_class.new('Foo')
-        3.times { instance.whitelist }
-
-        expect(instance.whitelist[:transformers].size).to eq 5
-      end
-
-      it 'allows syntax highlighting' do
-        exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>}
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'sanitizes `class` attribute from non-highlight spans' do
-        act = %q{<span class="k">def</span>}
-        expect(filter(act).to_html).to eq %q{<span>def</span>}
-      end
-
-      it 'allows `style` attribute on table elements' do
-        html = <<-HTML.strip_heredoc
-        <table>
-          <tr><th style="text-align: center">Head</th></tr>
-          <tr><td style="text-align: right">Body</th></tr>
-        </table>
-        HTML
-
-        doc = filter(html)
-
-        expect(doc.at_css('th')['style']).to eq 'text-align: center'
-        expect(doc.at_css('td')['style']).to eq 'text-align: right'
-      end
-
-      it 'allows `span` elements' do
-        exp = act = %q{<span>Hello</span>}
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'removes `rel` attribute from `a` elements' do
-        act = %q{<a href="#" rel="nofollow">Link</a>}
-        exp = %q{<a href="#">Link</a>}
-
-        expect(filter(act).to_html).to eq exp
-      end
-
-      # Adapted from the Sanitize test suite: http://git.io/vczrM
-      protocols = {
-        'protocol-based JS injection: simple, no spaces' => {
-          input:  '<a href="javascript:alert(\'XSS\');">foo</a>',
-          output: '<a>foo</a>'
-        },
-
-        'protocol-based JS injection: simple, spaces before' => {
-          input:  '<a href="javascript    :alert(\'XSS\');">foo</a>',
-          output: '<a>foo</a>'
-        },
-
-        'protocol-based JS injection: simple, spaces after' => {
-          input:  '<a href="javascript:    alert(\'XSS\');">foo</a>',
-          output: '<a>foo</a>'
-        },
-
-        'protocol-based JS injection: simple, spaces before and after' => {
-          input:  '<a href="javascript    :   alert(\'XSS\');">foo</a>',
-          output: '<a>foo</a>'
-        },
-
-        'protocol-based JS injection: preceding colon' => {
-          input:  '<a href=":javascript:alert(\'XSS\');">foo</a>',
-          output: '<a>foo</a>'
-        },
-
-        'protocol-based JS injection: UTF-8 encoding' => {
-          input:  '<a href="javascript&#58;">foo</a>',
-          output: '<a>foo</a>'
-        },
-
-        'protocol-based JS injection: long UTF-8 encoding' => {
-          input:  '<a href="javascript&#0058;">foo</a>',
-          output: '<a>foo</a>'
-        },
-
-        'protocol-based JS injection: long UTF-8 encoding without semicolons' => {
-          input:  '<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
-          output: '<a>foo</a>'
-        },
-
-        'protocol-based JS injection: hex encoding' => {
-          input:  '<a href="javascript&#x3A;">foo</a>',
-          output: '<a>foo</a>'
-        },
-
-        'protocol-based JS injection: long hex encoding' => {
-          input:  '<a href="javascript&#x003A;">foo</a>',
-          output: '<a>foo</a>'
-        },
-
-        'protocol-based JS injection: hex encoding without semicolons' => {
-          input:  '<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>foo</a>',
-          output: '<a>foo</a>'
-        },
-
-        'protocol-based JS injection: null char' => {
-          input:  "<a href=java\0script:alert(\"XSS\")>foo</a>",
-          output: '<a href="java"></a>'
-        },
-
-        'protocol-based JS injection: spaces and entities' => {
-          input:  '<a href=" &#14;  javascript:alert(\'XSS\');">foo</a>',
-          output: '<a href="">foo</a>'
-        },
-      }
-
-      protocols.each do |name, data|
-        it "handles #{name}" do
-          doc = filter(data[:input])
-
-          expect(doc.to_html).to eq data[:output]
-        end
-      end
-
-      it 'allows non-standard anchor schemes' do
-        exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>}
-        act = filter(exp)
-
-        expect(act.to_html).to eq exp
-      end
-
-      it 'allows relative links' do
-        exp = %q{<a href="foo/bar.md">foo/bar.md</a>}
-        act = filter(exp)
-
-        expect(act.to_html).to eq exp
-      end
-    end
-
-    context 'when pipeline is :description' do
-      it 'uses a stricter whitelist' do
-        doc = filter('<h1>Description</h1>', pipeline: :description)
-        expect(doc.to_html.strip).to eq 'Description'
-      end
-
-      %w(pre code img ol ul li).each do |elem|
-        it "removes '#{elem}' elements" do
-          act = "<#{elem}>Description</#{elem}>"
-          expect(filter(act, pipeline: :description).to_html.strip).
-            to eq 'Description'
-        end
-      end
-
-      %w(b i strong em a ins del sup sub p).each do |elem|
-        it "still allows '#{elem}' elements" do
-          exp = act = "<#{elem}>Description</#{elem}>"
-          expect(filter(act, pipeline: :description).to_html).to eq exp
-        end
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
deleted file mode 100644
index 9d9652dba46b1a3f0badd95de2cabdaa41cc6266..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe SnippetReferenceFilter do
-    include FilterSpecHelper
-
-    let(:project)   { create(:empty_project, :public) }
-    let(:snippet)   { create(:project_snippet, project: project) }
-    let(:reference) { snippet.to_reference }
-
-    it 'requires project context' do
-      expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
-    end
-
-    %w(pre code a style).each do |elem|
-      it "ignores valid references contained inside '#{elem}' element" do
-        exp = act = "<#{elem}>Snippet #{reference}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
-      end
-    end
-
-    context 'internal reference' do
-      it 'links to a valid reference' do
-        doc = filter("See #{reference}")
-
-        expect(doc.css('a').first.attr('href')).to eq urls.
-          namespace_project_snippet_url(project.namespace, project, snippet)
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("Snippet (#{reference}.)")
-        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
-      end
-
-      it 'ignores invalid snippet IDs' do
-        exp = act = "Snippet #{invalidate_reference(reference)}"
-
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'includes a title attribute' do
-        doc = filter("Snippet #{reference}")
-        expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
-      end
-
-      it 'escapes the title attribute' do
-        snippet.update_attribute(:title, %{"></a>whatever<a title="})
-
-        doc = filter("Snippet #{reference}")
-        expect(doc.text).to eq "Snippet #{reference}"
-      end
-
-      it 'includes default classes' do
-        doc = filter("Snippet #{reference}")
-        expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
-      end
-
-      it 'includes a data-project attribute' do
-        doc = filter("Snippet #{reference}")
-        link = doc.css('a').first
-
-        expect(link).to have_attribute('data-project')
-        expect(link.attr('data-project')).to eq project.id.to_s
-      end
-
-      it 'includes a data-snippet attribute' do
-        doc = filter("See #{reference}")
-        link = doc.css('a').first
-
-        expect(link).to have_attribute('data-snippet')
-        expect(link.attr('data-snippet')).to eq snippet.id.to_s
-      end
-
-      it 'supports an :only_path context' do
-        doc = filter("Snippet #{reference}", only_path: true)
-        link = doc.css('a').first.attr('href')
-
-        expect(link).not_to match %r(https?://)
-        expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("Snippet #{reference}")
-        expect(result[:references][:snippet]).to eq [snippet]
-      end
-    end
-
-    context 'cross-project reference' do
-      let(:namespace) { create(:namespace, name: 'cross-reference') }
-      let(:project2)  { create(:empty_project, :public, namespace: namespace) }
-      let(:snippet)   { create(:project_snippet, project: project2) }
-      let(:reference) { snippet.to_reference(project) }
-
-      it 'links to a valid reference' do
-        doc = filter("See #{reference}")
-
-        expect(doc.css('a').first.attr('href')).
-          to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
-      end
-
-      it 'links with adjacent text' do
-        doc = filter("See (#{reference}.)")
-        expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
-      end
-
-      it 'ignores invalid snippet IDs on the referenced project' do
-        exp = act = "See #{invalidate_reference(reference)}"
-
-        expect(filter(act).to_html).to eq exp
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("Snippet #{reference}")
-        expect(result[:references][:snippet]).to eq [snippet]
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb b/spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb
deleted file mode 100644
index 6a490673728378dcac545b4e14532d2228753b34..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe SyntaxHighlightFilter do
-    include FilterSpecHelper
-
-    it 'highlights valid code blocks' do
-      result = filter('<pre><code>def fun end</code>')
-      expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>\n")
-    end
-
-    it 'passes through invalid code blocks' do
-      allow_any_instance_of(SyntaxHighlightFilter).to receive(:block_code).and_raise(StandardError)
-
-      result = filter('<pre><code>This is a test</code></pre>')
-      expect(result.to_html).to eq('<pre>This is a test</pre>')
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb b/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
deleted file mode 100644
index ddf583a72c12501bd059b36a94f47a07011a5a2d..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-# encoding: UTF-8
-
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe TableOfContentsFilter do
-    include FilterSpecHelper
-
-    def header(level, text)
-      "<h#{level}>#{text}</h#{level}>\n"
-    end
-
-    it 'does nothing when :no_header_anchors is truthy' do
-      exp = act = header(1, 'Header')
-      expect(filter(act, no_header_anchors: 1).to_html).to eq exp
-    end
-
-    it 'does nothing with empty headers' do
-      exp = act = header(1, nil)
-      expect(filter(act).to_html).to eq exp
-    end
-
-    1.upto(6) do |i|
-      it "processes h#{i} elements" do
-        html = header(i, "Header #{i}")
-        doc = filter(html)
-
-        expect(doc.css("h#{i} a").first.attr('id')).to eq "header-#{i}"
-      end
-    end
-
-    describe 'anchor tag' do
-      it 'has an `anchor` class' do
-        doc = filter(header(1, 'Header'))
-        expect(doc.css('h1 a').first.attr('class')).to eq 'anchor'
-      end
-
-      it 'links to the id' do
-        doc = filter(header(1, 'Header'))
-        expect(doc.css('h1 a').first.attr('href')).to eq '#header'
-      end
-
-      describe 'generated IDs' do
-        it 'translates spaces to dashes' do
-          doc = filter(header(1, 'This header has spaces in it'))
-          expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-has-spaces-in-it'
-        end
-
-        it 'squeezes multiple spaces and dashes' do
-          doc = filter(header(1, 'This---header     is poorly-formatted'))
-          expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-poorly-formatted'
-        end
-
-        it 'removes punctuation' do
-          doc = filter(header(1, "This, header! is, filled. with @ punctuation?"))
-          expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-filled-with-punctuation'
-        end
-
-        it 'appends a unique number to duplicates' do
-          doc = filter(header(1, 'One') + header(2, 'One'))
-
-          expect(doc.css('h1 a').first.attr('id')).to eq 'one'
-          expect(doc.css('h2 a').first.attr('id')).to eq 'one-1'
-        end
-
-        it 'supports Unicode' do
-          doc = filter(header(1, '한글'))
-          expect(doc.css('h1 a').first.attr('id')).to eq '한글'
-          expect(doc.css('h1 a').first.attr('href')).to eq '#한글'
-        end
-      end
-    end
-
-    describe 'result' do
-      def result(html)
-        HTML::Pipeline.new([described_class]).call(html)
-      end
-
-      let(:results) { result(header(1, 'Header 1') + header(2, 'Header 2')) }
-      let(:doc) { Nokogiri::XML::DocumentFragment.parse(results[:toc]) }
-
-      it 'is contained within a `ul` element' do
-        expect(doc.children.first.name).to eq 'ul'
-        expect(doc.children.first.attr('class')).to eq 'section-nav'
-      end
-
-      it 'contains an `li` element for each header' do
-        expect(doc.css('li').length).to eq 2
-
-        links = doc.css('li a')
-
-        expect(links.first.attr('href')).to eq '#header-1'
-        expect(links.first.text).to eq 'Header 1'
-        expect(links.last.attr('href')).to eq '#header-2'
-        expect(links.last.text).to eq 'Header 2'
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/task_list_filter_spec.rb b/spec/lib/gitlab/markdown/task_list_filter_spec.rb
deleted file mode 100644
index 94f39cc966ed55ed6903c85fd01842a19d08acae..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/task_list_filter_spec.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe TaskListFilter do
-    include FilterSpecHelper
-
-    it 'does not apply `task-list` class to non-task lists' do
-      exp = act = %(<ul><li>Item</li></ul>)
-      expect(filter(act).to_html).to eq exp
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/upload_link_filter_spec.rb b/spec/lib/gitlab/markdown/upload_link_filter_spec.rb
deleted file mode 100644
index 9ae45a6f5597e035e11e393b5424f34fca221ce9..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/upload_link_filter_spec.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# encoding: UTF-8
-
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe UploadLinkFilter do
-    def filter(doc, contexts = {})
-      contexts.reverse_merge!({
-        project: project
-      })
-
-      described_class.call(doc, contexts)
-    end
-
-    def image(path)
-      %(<img src="#{path}" />)
-    end
-
-    def link(path)
-      %(<a href="#{path}">#{path}</a>)
-    end
-
-    let(:project) { create(:project) }
-
-    shared_examples :preserve_unchanged do
-      it 'does not modify any relative URL in anchor' do
-        doc = filter(link('README.md'))
-        expect(doc.at_css('a')['href']).to eq 'README.md'
-      end
-
-      it 'does not modify any relative URL in image' do
-        doc = filter(image('files/images/logo-black.png'))
-        expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
-      end
-    end
-
-    it 'does not raise an exception on invalid URIs' do
-      act = link("://foo")
-      expect { filter(act) }.not_to raise_error
-    end
-
-    context 'with a valid repository' do
-      it 'rebuilds relative URL for a link' do
-        doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
-        expect(doc.at_css('a')['href']).
-          to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
-      end
-
-      it 'rebuilds relative URL for an image' do
-        doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
-        expect(doc.at_css('a')['href']).
-          to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
-      end
-
-      it 'does not modify absolute URL' do
-        doc = filter(link('http://example.com'))
-        expect(doc.at_css('a')['href']).to eq 'http://example.com'
-      end
-
-      it 'supports Unicode filenames' do
-        path = '/uploads/한글.png'
-        escaped = Addressable::URI.escape(path)
-
-        # Stub these methods so the file doesn't actually need to be in the repo
-        allow_any_instance_of(described_class).
-          to receive(:file_exists?).and_return(true)
-        allow_any_instance_of(described_class).
-          to receive(:image?).with(path).and_return(true)
-
-        doc = filter(image(escaped))
-        expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png"
-      end
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
deleted file mode 100644
index d9e0d7c42db7fd1bc83981d4b5bdeb0600596fbd..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
-  describe UserReferenceFilter do
-    include FilterSpecHelper
-
-    let(:project)   { create(:empty_project, :public) }
-    let(:user)      { create(:user) }
-    let(:reference) { user.to_reference }
-
-    it 'requires project context' do
-      expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
-    end
-
-    it 'ignores invalid users' do
-      exp = act = "Hey #{invalidate_reference(reference)}"
-      expect(filter(act).to_html).to eq(exp)
-    end
-
-    %w(pre code a style).each do |elem|
-      it "ignores valid references contained inside '#{elem}' element" do
-        exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
-        expect(filter(act).to_html).to eq exp
-      end
-    end
-
-    context 'mentioning @all' do
-      let(:reference) { User.reference_prefix + 'all' }
-
-      before do
-        project.team << [project.creator, :developer]
-      end
-
-      it 'supports a special @all mention' do
-        doc = filter("Hey #{reference}")
-        expect(doc.css('a').length).to eq 1
-        expect(doc.css('a').first.attr('href'))
-          .to eq urls.namespace_project_url(project.namespace, project)
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("Hey #{reference}")
-        expect(result[:references][:user]).to eq [project.creator]
-      end
-    end
-
-    context 'mentioning a user' do
-      it 'links to a User' do
-        doc = filter("Hey #{reference}")
-        expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
-      end
-
-      it 'links to a User with a period' do
-        user = create(:user, name: 'alphA.Beta')
-
-        doc = filter("Hey #{user.to_reference}")
-        expect(doc.css('a').length).to eq 1
-      end
-
-      it 'links to a User with an underscore' do
-        user = create(:user, name: 'ping_pong_king')
-
-        doc = filter("Hey #{user.to_reference}")
-        expect(doc.css('a').length).to eq 1
-      end
-
-      it 'includes a data-user attribute' do
-        doc = filter("Hey #{reference}")
-        link = doc.css('a').first
-
-        expect(link).to have_attribute('data-user')
-        expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("Hey #{reference}")
-        expect(result[:references][:user]).to eq [user]
-      end
-    end
-
-    context 'mentioning a group' do
-      let(:group)     { create(:group) }
-      let(:reference) { group.to_reference }
-
-      it 'links to the Group' do
-        doc = filter("Hey #{reference}")
-        expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
-      end
-
-      it 'includes a data-group attribute' do
-        doc = filter("Hey #{reference}")
-        link = doc.css('a').first
-
-        expect(link).to have_attribute('data-group')
-        expect(link.attr('data-group')).to eq group.id.to_s
-      end
-
-      it 'adds to the results hash' do
-        result = reference_pipeline_result("Hey #{reference}")
-        expect(result[:references][:user]).to eq group.users
-      end
-    end
-
-    it 'links with adjacent text' do
-      doc = filter("Mention me (#{reference}.)")
-      expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
-    end
-
-    it 'includes default classes' do
-      doc = filter("Hey #{reference}")
-      expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
-    end
-
-    it 'supports an :only_path context' do
-      doc = filter("Hey #{reference}", only_path: true)
-      link = doc.css('a').first.attr('href')
-
-      expect(link).not_to match %r(https?://)
-      expect(link).to eq urls.user_path(user)
-    end
-  end
-end
diff --git a/spec/lib/gitlab/markup_helper_spec.rb b/spec/lib/gitlab/markup_helper_spec.rb
index e610fab05da70e6e67ef9f8fc87624ec07539680..93b91b849f2837956970af736f15287aae7bd663 100644
--- a/spec/lib/gitlab/markup_helper_spec.rb
+++ b/spec/lib/gitlab/markup_helper_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::MarkupHelper do
+describe Gitlab::MarkupHelper, lib: true do
   describe '#markup?' do
     %w(textile rdoc org creole wiki
        mediawiki rst adoc ad asciidoc mdown md markdown).each do |type|
diff --git a/spec/lib/gitlab/metrics/delta_spec.rb b/spec/lib/gitlab/metrics/delta_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..718387cdee1785a272c5b34b53de87f09f97e4c6
--- /dev/null
+++ b/spec/lib/gitlab/metrics/delta_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Delta do
+  let(:delta) { described_class.new }
+
+  describe '#compared_with' do
+    it 'returns the delta as a Numeric' do
+      expect(delta.compared_with(5)).to eq(5)
+    end
+
+    it 'bases the delta on a previously used value' do
+      expect(delta.compared_with(5)).to eq(5)
+      expect(delta.compared_with(15)).to eq(10)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a7eab9d11cc76f531a5efe03b1a739c56455fc2a
--- /dev/null
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -0,0 +1,234 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Instrumentation do
+  let(:transaction) { Gitlab::Metrics::Transaction.new }
+
+  before do
+    @dummy = Class.new do
+      def self.foo(text = 'foo')
+        text
+      end
+
+      def bar(text = 'bar')
+        text
+      end
+    end
+
+    allow(@dummy).to receive(:name).and_return('Dummy')
+  end
+
+  describe '.configure' do
+    it 'yields self' do
+      described_class.configure do |c|
+        expect(c).to eq(described_class)
+      end
+    end
+  end
+
+  describe '.instrument_method' do
+    describe 'with metrics enabled' do
+      before do
+        allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
+
+        described_class.instrument_method(@dummy, :foo)
+      end
+
+      it 'renames the original method' do
+        expect(@dummy).to respond_to(:_original_foo)
+      end
+
+      it 'calls the instrumented method with the correct arguments' do
+        expect(@dummy.foo).to eq('foo')
+      end
+
+      it 'tracks the call duration upon calling the method' do
+        allow(Gitlab::Metrics).to receive(:method_call_threshold).
+          and_return(0)
+
+        allow(described_class).to receive(:transaction).
+          and_return(transaction)
+
+        expect(transaction).to receive(:add_metric).
+          with(described_class::SERIES, an_instance_of(Hash),
+               method: 'Dummy.foo')
+
+        @dummy.foo
+      end
+
+      it 'does not track method calls below a given duration threshold' do
+        allow(Gitlab::Metrics).to receive(:method_call_threshold).
+          and_return(100)
+
+        expect(transaction).to_not receive(:add_metric)
+
+        @dummy.foo
+      end
+    end
+
+    describe 'with metrics disabled' do
+      before do
+        allow(Gitlab::Metrics).to receive(:enabled?).and_return(false)
+      end
+
+      it 'does not instrument the method' do
+        described_class.instrument_method(@dummy, :foo)
+
+        expect(@dummy).to_not respond_to(:_original_foo)
+      end
+    end
+  end
+
+  describe '.instrument_instance_method' do
+    describe 'with metrics enabled' do
+      before do
+        allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
+
+        described_class.
+          instrument_instance_method(@dummy, :bar)
+      end
+
+      it 'renames the original method' do
+        expect(@dummy.method_defined?(:_original_bar)).to eq(true)
+      end
+
+      it 'calls the instrumented method with the correct arguments' do
+        expect(@dummy.new.bar).to eq('bar')
+      end
+
+      it 'tracks the call duration upon calling the method' do
+        allow(Gitlab::Metrics).to receive(:method_call_threshold).
+          and_return(0)
+
+        allow(described_class).to receive(:transaction).
+          and_return(transaction)
+
+        expect(transaction).to receive(:add_metric).
+          with(described_class::SERIES, an_instance_of(Hash),
+               method: 'Dummy#bar')
+
+        @dummy.new.bar
+      end
+
+      it 'does not track method calls below a given duration threshold' do
+        allow(Gitlab::Metrics).to receive(:method_call_threshold).
+          and_return(100)
+
+        expect(transaction).to_not receive(:add_metric)
+
+        @dummy.new.bar
+      end
+    end
+
+    describe 'with metrics disabled' do
+      before do
+        allow(Gitlab::Metrics).to receive(:enabled?).and_return(false)
+      end
+
+      it 'does not instrument the method' do
+        described_class.
+          instrument_instance_method(@dummy, :bar)
+
+        expect(@dummy.method_defined?(:_original_bar)).to eq(false)
+      end
+    end
+  end
+
+  describe '.instrument_class_hierarchy' do
+    before do
+      allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
+
+      @child1 = Class.new(@dummy) do
+        def self.child1_foo; end
+        def child1_bar; end
+      end
+
+      @child2 = Class.new(@child1) do
+        def self.child2_foo; end
+        def child2_bar; end
+      end
+    end
+
+    it 'recursively instruments a class hierarchy' do
+      described_class.instrument_class_hierarchy(@dummy)
+
+      expect(@child1).to respond_to(:_original_child1_foo)
+      expect(@child2).to respond_to(:_original_child2_foo)
+
+      expect(@child1.method_defined?(:_original_child1_bar)).to eq(true)
+      expect(@child2.method_defined?(:_original_child2_bar)).to eq(true)
+    end
+
+    it 'does not instrument the root module' do
+      described_class.instrument_class_hierarchy(@dummy)
+
+      expect(@dummy).to_not respond_to(:_original_foo)
+      expect(@dummy.method_defined?(:_original_bar)).to eq(false)
+    end
+  end
+
+  describe '.instrument_methods' do
+    before do
+      allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
+    end
+
+    it 'instruments all public class methods' do
+      described_class.instrument_methods(@dummy)
+
+      expect(@dummy).to respond_to(:_original_foo)
+    end
+
+    it 'only instruments methods directly defined in the module' do
+      mod = Module.new do
+        def kittens
+        end
+      end
+
+      @dummy.extend(mod)
+
+      described_class.instrument_methods(@dummy)
+
+      expect(@dummy).to_not respond_to(:_original_kittens)
+    end
+
+    it 'can take a block to determine if a method should be instrumented' do
+      described_class.instrument_methods(@dummy) do
+        false
+      end
+
+      expect(@dummy).to_not respond_to(:_original_foo)
+    end
+  end
+
+  describe '.instrument_instance_methods' do
+    before do
+      allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
+    end
+
+    it 'instruments all public instance methods' do
+      described_class.instrument_instance_methods(@dummy)
+
+      expect(@dummy.method_defined?(:_original_bar)).to eq(true)
+    end
+
+    it 'only instruments methods directly defined in the module' do
+      mod = Module.new do
+        def kittens
+        end
+      end
+
+      @dummy.include(mod)
+
+      described_class.instrument_instance_methods(@dummy)
+
+      expect(@dummy.method_defined?(:_original_kittens)).to eq(false)
+    end
+
+    it 'can take a block to determine if a method should be instrumented' do
+      described_class.instrument_instance_methods(@dummy) do
+        false
+      end
+
+      expect(@dummy.method_defined?(:_original_bar)).to eq(false)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics/metric_spec.rb b/spec/lib/gitlab/metrics/metric_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ec39bc9cce8d448bc8a04cfefc5c31c3588aed54
--- /dev/null
+++ b/spec/lib/gitlab/metrics/metric_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Metric do
+  let(:metric) do
+    described_class.new('foo', { number: 10 }, { host: 'localtoast' })
+  end
+
+  describe '#series' do
+    subject { metric.series }
+
+    it { is_expected.to eq('foo') }
+  end
+
+  describe '#values' do
+    subject { metric.values }
+
+    it { is_expected.to eq({ number: 10 }) }
+  end
+
+  describe '#tags' do
+    subject { metric.tags }
+
+    it { is_expected.to eq({ host: 'localtoast' }) }
+  end
+
+  describe '#to_hash' do
+    it 'returns a Hash' do
+      expect(metric.to_hash).to be_an_instance_of(Hash)
+    end
+
+    describe 'the returned Hash' do
+      let(:hash) { metric.to_hash }
+
+      it 'includes the series' do
+        expect(hash[:series]).to eq('foo')
+      end
+
+      it 'includes the tags' do
+        expect(hash[:tags]).to be_an_instance_of(Hash)
+
+        expect(hash[:tags][:hostname]).to be_an_instance_of(String)
+        expect(hash[:tags][:ruby_engine]).to be_an_instance_of(String)
+        expect(hash[:tags][:ruby_version]).to be_an_instance_of(String)
+        expect(hash[:tags][:gitlab_version]).to be_an_instance_of(String)
+        expect(hash[:tags][:process_type]).to be_an_instance_of(String)
+      end
+
+      it 'includes the values' do
+        expect(hash[:values]).to eq({ number: 10 })
+      end
+
+      it 'includes the timestamp' do
+        expect(hash[:timestamp]).to be_an_instance_of(Fixnum)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb b/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2b681c9fe347b2a4f15026c84fd6240fda25795f
--- /dev/null
+++ b/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb
@@ -0,0 +1,93 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::ObfuscatedSQL do
+  describe '#to_s' do
+    it 'replaces newlines with a space' do
+      sql = described_class.new("SELECT x\nFROM y")
+
+      expect(sql.to_s).to eq('SELECT x FROM y')
+    end
+
+    describe 'using single values' do
+      it 'replaces a single integer' do
+        sql = described_class.new('SELECT x FROM y WHERE a = 10')
+
+        expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
+      end
+
+      it 'replaces a single float' do
+        sql = described_class.new('SELECT x FROM y WHERE a = 10.5')
+
+        expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
+      end
+
+      it 'replaces a single quoted string' do
+        sql = described_class.new("SELECT x FROM y WHERE a = 'foo'")
+
+        expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
+      end
+
+      if Gitlab::Database.mysql?
+        it 'replaces a double quoted string' do
+          sql = described_class.new('SELECT x FROM y WHERE a = "foo"')
+
+          expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
+        end
+      end
+
+      it 'replaces a single regular expression' do
+        sql = described_class.new('SELECT x FROM y WHERE a = /foo/')
+
+        expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
+      end
+
+      it 'replaces regular expressions using escaped slashes' do
+        sql = described_class.new('SELECT x FROM y WHERE a = /foo\/bar/')
+
+        expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
+      end
+    end
+
+    describe 'using consecutive values' do
+      it 'replaces multiple integers' do
+        sql = described_class.new('SELECT x FROM y WHERE z IN (10, 20, 30)')
+
+        expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (3 values)')
+      end
+
+      it 'replaces multiple floats' do
+        sql = described_class.new('SELECT x FROM y WHERE z IN (1.5, 2.5, 3.5)')
+
+        expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (3 values)')
+      end
+
+      it 'replaces multiple single quoted strings' do
+        sql = described_class.new("SELECT x FROM y WHERE z IN ('foo', 'bar')")
+
+        expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)')
+      end
+
+      if Gitlab::Database.mysql?
+        it 'replaces multiple double quoted strings' do
+          sql = described_class.new('SELECT x FROM y WHERE z IN ("foo", "bar")')
+
+          expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)')
+        end
+      end
+
+      it 'replaces multiple regular expressions' do
+        sql = described_class.new('SELECT x FROM y WHERE z IN (/foo/, /bar/)')
+
+        expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)')
+      end
+    end
+
+    if Gitlab::Database.postgresql?
+      it 'replaces double quotes' do
+        sql = described_class.new('SELECT "x" FROM "y"')
+
+        expect(sql.to_s).to eq('SELECT x FROM y')
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a143fe4cfcd68a8f4de75b6ae85b393125025a9f
--- /dev/null
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::RackMiddleware do
+  let(:app) { double(:app) }
+
+  let(:middleware) { described_class.new(app) }
+
+  let(:env) { { 'REQUEST_METHOD' => 'GET', 'REQUEST_URI' => '/foo' } }
+
+  describe '#call' do
+    before do
+      expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish)
+    end
+
+    it 'tracks a transaction' do
+      expect(app).to receive(:call).with(env).and_return('yay')
+
+      expect(middleware.call(env)).to eq('yay')
+    end
+
+    it 'tags a transaction with the name and action of a controller' do
+      klass      = double(:klass, name: 'TestController')
+      controller = double(:controller, class: klass, action_name: 'show')
+
+      env['action_controller.instance'] = controller
+
+      allow(app).to receive(:call).with(env)
+
+      expect(middleware).to receive(:tag_controller).
+        with(an_instance_of(Gitlab::Metrics::Transaction), env)
+
+      middleware.call(env)
+    end
+  end
+
+  describe '#transaction_from_env' do
+    let(:transaction) { middleware.transaction_from_env(env) }
+
+    it 'returns a Transaction' do
+      expect(transaction).to be_an_instance_of(Gitlab::Metrics::Transaction)
+    end
+
+    it 'tags the transaction with the request method and URI' do
+      expect(transaction.tags[:request_method]).to eq('GET')
+      expect(transaction.tags[:request_uri]).to eq('/foo')
+    end
+  end
+
+  describe '#tag_controller' do
+    let(:transaction) { middleware.transaction_from_env(env) }
+
+    it 'tags a transaction with the name and action of a controller' do
+      klass      = double(:klass, name: 'TestController')
+      controller = double(:controller, class: klass, action_name: 'show')
+
+      env['action_controller.instance'] = controller
+
+      middleware.tag_controller(transaction, env)
+
+      expect(transaction.tags[:action]).to eq('TestController#show')
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/sampler_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..51a941c48cd7ea01385901f3f099836e154d0d27
--- /dev/null
+++ b/spec/lib/gitlab/metrics/sampler_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Sampler do
+  let(:sampler) { described_class.new(5) }
+
+  after do
+    Allocations.stop if Gitlab::Metrics.mri?
+  end
+
+  describe '#start' do
+    it 'gathers a sample at a given interval' do
+      expect(sampler).to receive(:sleep).with(5)
+      expect(sampler).to receive(:sample)
+      expect(sampler).to receive(:loop).and_yield
+
+      sampler.start.join
+    end
+  end
+
+  describe '#sample' do
+    it 'samples various statistics' do
+      expect(sampler).to receive(:sample_memory_usage)
+      expect(sampler).to receive(:sample_file_descriptors)
+      expect(sampler).to receive(:sample_objects)
+      expect(sampler).to receive(:sample_gc)
+      expect(sampler).to receive(:flush)
+
+      sampler.sample
+    end
+
+    it 'clears any GC profiles' do
+      expect(sampler).to receive(:flush)
+      expect(GC::Profiler).to receive(:clear)
+
+      sampler.sample
+    end
+  end
+
+  describe '#flush' do
+    it 'schedules the metrics using Sidekiq' do
+      expect(Gitlab::Metrics).to receive(:submit_metrics).
+        with([an_instance_of(Hash)])
+
+      sampler.sample_memory_usage
+      sampler.flush
+    end
+  end
+
+  describe '#sample_memory_usage' do
+    it 'adds a metric containing the memory usage' do
+      expect(Gitlab::Metrics::System).to receive(:memory_usage).
+        and_return(9000)
+
+      expect(Gitlab::Metrics::Metric).to receive(:new).
+        with('memory_usage', value: 9000).
+        and_call_original
+
+      sampler.sample_memory_usage
+    end
+  end
+
+  describe '#sample_file_descriptors' do
+    it 'adds a metric containing the amount of open file descriptors' do
+      expect(Gitlab::Metrics::System).to receive(:file_descriptor_count).
+        and_return(4)
+
+      expect(Gitlab::Metrics::Metric).to receive(:new).
+        with('file_descriptors', value: 4).
+        and_call_original
+
+      sampler.sample_file_descriptors
+    end
+  end
+
+  describe '#sample_objects' do
+    it 'adds a metric containing the amount of allocated objects' do
+      expect(Gitlab::Metrics::Metric).to receive(:new).
+        with('object_counts', an_instance_of(Hash), an_instance_of(Hash)).
+        at_least(:once).
+        and_call_original
+
+      sampler.sample_objects
+    end
+  end
+
+  describe '#sample_gc' do
+    it 'adds a metric containing garbage collection statistics' do
+      expect(GC::Profiler).to receive(:total_time).and_return(0.24)
+
+      expect(Gitlab::Metrics::Metric).to receive(:new).
+        with('gc_statistics', an_instance_of(Hash)).
+        and_call_original
+
+      sampler.sample_gc
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5882e7d81c7c55059a6d71836ae937c6ecbeaede
--- /dev/null
+++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::SidekiqMiddleware do
+  let(:middleware) { described_class.new }
+
+  describe '#call' do
+    it 'tracks the transaction' do
+      worker = Class.new.new
+
+      expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish)
+
+      middleware.call(worker, 'test', :test) { nil }
+    end
+  end
+
+  describe '#tag_worker' do
+    it 'adds the worker class and action to the transaction' do
+      trans  = Gitlab::Metrics::Transaction.new
+      worker = double(:worker, class: double(:class, name: 'TestWorker'))
+
+      expect(trans).to receive(:add_tag).with(:action, 'TestWorker#perform')
+
+      middleware.tag_worker(trans, worker)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c6cd584663f26344861d6a20897e5a4f7b3c5dfe
--- /dev/null
+++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Subscribers::ActionView do
+  let(:transaction) { Gitlab::Metrics::Transaction.new }
+
+  let(:subscriber) { described_class.new }
+
+  let(:event) do
+    root = Rails.root.to_s
+
+    double(:event, duration: 2.1,
+                   payload:  { identifier: "#{root}/app/views/x.html.haml" })
+  end
+
+  before do
+    allow(subscriber).to receive(:current_transaction).and_return(transaction)
+
+    allow(Gitlab::Metrics).to receive(:last_relative_application_frame).
+      and_return(['app/views/x.html.haml', 4])
+  end
+
+  describe '#render_template' do
+    it 'tracks rendering of a template' do
+      values = { duration: 2.1 }
+      tags   = {
+        view: 'app/views/x.html.haml',
+        file: 'app/views/x.html.haml',
+        line: 4
+      }
+
+      expect(transaction).to receive(:add_metric).
+        with(described_class::SERIES, values, tags)
+
+      subscriber.render_template(event)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..05b6cc147162c76eb9533dd991aa4fa731be5f5c
--- /dev/null
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Subscribers::ActiveRecord do
+  let(:transaction) { Gitlab::Metrics::Transaction.new }
+
+  let(:subscriber) { described_class.new }
+
+  let(:event) do
+    double(:event, duration: 0.2,
+                   payload:  { sql: 'SELECT * FROM users WHERE id = 10' })
+  end
+
+  before do
+    allow(subscriber).to receive(:current_transaction).and_return(transaction)
+
+    allow(Gitlab::Metrics).to receive(:last_relative_application_frame).
+      and_return(['app/models/foo.rb', 4])
+  end
+
+  describe '#sql' do
+    it 'tracks the execution of a SQL query' do
+      sql    = 'SELECT * FROM users WHERE id = ?'
+      values = { duration: 0.2 }
+      tags   = { sql: sql, file: 'app/models/foo.rb', line: 4 }
+
+      expect(transaction).to receive(:add_metric).
+        with(described_class::SERIES, values, tags)
+
+      subscriber.sql(event)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f8c1d956ca1fb9e0c5cb41d2e7e2b6b7e217dfbd
--- /dev/null
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::System do
+  if File.exist?('/proc')
+    describe '.memory_usage' do
+      it "returns the process' memory usage in bytes" do
+        expect(described_class.memory_usage).to be > 0
+      end
+    end
+
+    describe '.file_descriptor_count' do
+      it 'returns the amount of open file descriptors' do
+        expect(described_class.file_descriptor_count).to be > 0
+      end
+    end
+  else
+    describe '.memory_usage' do
+      it 'returns 0.0' do
+        expect(described_class.memory_usage).to eq(0.0)
+      end
+    end
+
+    describe '.file_descriptor_count' do
+      it 'returns 0' do
+        expect(described_class.file_descriptor_count).to eq(0)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6862fc9e2d19d1939cca2b68478f4fda12f58512
--- /dev/null
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Transaction do
+  let(:transaction) { described_class.new }
+
+  describe '#duration' do
+    it 'returns the duration of a transaction in seconds' do
+      transaction.run { sleep(0.5) }
+
+      expect(transaction.duration).to be >= 0.5
+    end
+  end
+
+  describe '#run' do
+    it 'yields the supplied block' do
+      expect { |b| transaction.run(&b) }.to yield_control
+    end
+
+    it 'stores the transaction in the current thread' do
+      transaction.run do
+        expect(Thread.current[described_class::THREAD_KEY]).to eq(transaction)
+      end
+    end
+
+    it 'removes the transaction from the current thread upon completion' do
+      transaction.run { }
+
+      expect(Thread.current[described_class::THREAD_KEY]).to be_nil
+    end
+  end
+
+  describe '#add_metric' do
+    it 'adds a metric tagged with the transaction UUID' do
+      expect(Gitlab::Metrics::Metric).to receive(:new).
+        with('foo', { number: 10 }, { transaction_id: transaction.uuid })
+
+      transaction.add_metric('foo', number: 10)
+    end
+  end
+
+  describe '#add_tag' do
+    it 'adds a tag' do
+      transaction.add_tag(:foo, 'bar')
+
+      expect(transaction.tags).to eq({ foo: 'bar' })
+    end
+  end
+
+  describe '#finish' do
+    it 'tracks the transaction details and submits them to Sidekiq' do
+      expect(transaction).to receive(:track_self)
+      expect(transaction).to receive(:submit)
+
+      transaction.finish
+    end
+  end
+
+  describe '#track_self' do
+    it 'adds a metric for the transaction itself' do
+      expect(transaction).to receive(:add_metric).
+        with(described_class::SERIES, { duration: transaction.duration }, {})
+
+      transaction.track_self
+    end
+  end
+
+  describe '#submit' do
+    it 'submits the metrics to Sidekiq' do
+      transaction.track_self
+
+      expect(Gitlab::Metrics).to receive(:submit_metrics).
+        with([an_instance_of(Hash)])
+
+      transaction.submit
+    end
+  end
+end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6c0682cac4dcdd439e547d4cf7f42caa7c16a9db
--- /dev/null
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -0,0 +1,84 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics do
+  describe '.pool_size' do
+    it 'returns a Fixnum' do
+      expect(described_class.pool_size).to be_an_instance_of(Fixnum)
+    end
+  end
+
+  describe '.timeout' do
+    it 'returns a Fixnum' do
+      expect(described_class.timeout).to be_an_instance_of(Fixnum)
+    end
+  end
+
+  describe '.enabled?' do
+    it 'returns a boolean' do
+      expect([true, false].include?(described_class.enabled?)).to eq(true)
+    end
+  end
+
+  describe '.hostname' do
+    it 'returns a String containing the hostname' do
+      expect(described_class.hostname).to eq(Socket.gethostname)
+    end
+  end
+
+  describe '.last_relative_application_frame' do
+    it 'returns an Array containing a file path and line number' do
+      file, line = described_class.last_relative_application_frame
+
+      expect(line).to eq(__LINE__ - 2)
+      expect(file).to eq('spec/lib/gitlab/metrics_spec.rb')
+    end
+  end
+
+  describe '#submit_metrics' do
+    it 'prepares and writes the metrics to InfluxDB' do
+      connection = double(:connection)
+      pool       = double(:pool)
+
+      expect(pool).to receive(:with).and_yield(connection)
+      expect(connection).to receive(:write_points).with(an_instance_of(Array))
+      expect(Gitlab::Metrics).to receive(:pool).and_return(pool)
+
+      described_class.submit_metrics([{ 'series' => 'kittens', 'tags' => {} }])
+    end
+  end
+
+  describe '#prepare_metrics' do
+    it 'returns a Hash with the keys as Symbols' do
+      metrics = described_class.
+        prepare_metrics([{ 'values' => {}, 'tags' => {} }])
+
+      expect(metrics).to eq([{ values: {}, tags: {} }])
+    end
+
+    it 'escapes tag values' do
+      metrics = described_class.prepare_metrics([
+        { 'values' => {}, 'tags' => { 'foo' => 'bar=' } }
+      ])
+
+      expect(metrics).to eq([{ values: {}, tags: { 'foo' => 'bar\\=' } }])
+    end
+
+    it 'drops empty tags' do
+      metrics = described_class.prepare_metrics([
+        { 'values' => {}, 'tags' => { 'cats' => '', 'dogs' => nil } }
+      ])
+
+      expect(metrics).to eq([{ values: {}, tags: {} }])
+    end
+  end
+
+  describe '#escape_value' do
+    it 'escapes an equals sign' do
+      expect(described_class.escape_value('foo=')).to eq('foo\\=')
+    end
+
+    it 'casts values to Strings' do
+      expect(described_class.escape_value(10)).to eq('10')
+    end
+  end
+end
diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb
index 448cd0c6880dcd58c23174cc62de6deec7453c2a..6cbdae737f4ef679c50bb60527bda6ef12834af5 100644
--- a/spec/lib/gitlab/note_data_builder_spec.rb
+++ b/spec/lib/gitlab/note_data_builder_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'Gitlab::NoteDataBuilder' do
+describe 'Gitlab::NoteDataBuilder', lib: true do
   let(:project) { create(:project) }
   let(:user) { create(:user) }
   let(:data) { Gitlab::NoteDataBuilder.build(note, user) }
diff --git a/spec/lib/gitlab/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/o_auth/auth_hash_spec.rb
index 5632f2306ec6f5ce037e5f46957db3b449161e89..8aaeb5779d300a293378358be8627eb8f6fabfea 100644
--- a/spec/lib/gitlab/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/o_auth/auth_hash_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::OAuth::AuthHash do
+describe Gitlab::OAuth::AuthHash, lib: true do
   let(:auth_hash) do
     Gitlab::OAuth::AuthHash.new(
       OmniAuth::AuthHash.new(
@@ -14,7 +14,7 @@ describe Gitlab::OAuth::AuthHash do
   let(:uid_raw) do
     "CN=Onur K\xC3\xBC\xC3\xA7\xC3\xBCk,OU=Test,DC=example,DC=net"
   end
-  let(:email_raw) { "onur.k\xC3\xBC\xC3\xA7\xC3\xBCk@example.net" }
+  let(:email_raw) { "onur.k\xC3\xBC\xC3\xA7\xC3\xBCk_ABC-123@example.net" }
   let(:nickname_raw) { "ok\xC3\xBC\xC3\xA7\xC3\xBCk" }
   let(:first_name_raw) { 'Onur' }
   let(:last_name_raw) { "K\xC3\xBC\xC3\xA7\xC3\xBCk" }
@@ -66,7 +66,7 @@ describe Gitlab::OAuth::AuthHash do
     before { info_hash.delete(:nickname) }
 
     it 'takes the first part of the email as username' do
-      expect(auth_hash.username).to eql 'onur-kucuk'
+      expect(auth_hash.username).to eql 'onur.kucuk_ABC-123'
     end
   end
 
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index fd3ab1fb7c86808e53f9639460605cb19a2da47e..925bc442a903138459e5f1b56c154733835fe4cf 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::OAuth::User do
+describe Gitlab::OAuth::User, lib: true do
   let(:oauth_user) { Gitlab::OAuth::User.new(auth_hash) }
   let(:gl_user) { oauth_user.gl_user }
   let(:uid) { 'my-uid' }
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index e53efec6c67c4f5734652b55de4d443b17b6dccb..795cf241278aad1abd989a6b1d1b2ef39b0c5ff1 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'Gitlab::Popen', no_db: true do
+describe 'Gitlab::Popen', lib: true, no_db: true do
   let(:path) { Rails.root.join('tmp').to_s }
 
   before do
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 19327ac8ce030f2622e2a3ba7dac59b9c41cc430..efc2e5f4ef1fda1ee96566c9d2c7a12daf2471d3 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ProjectSearchResults do
+describe Gitlab::ProjectSearchResults, lib: true do
   let(:project) { create(:project) }
   let(:query) { 'hello world' }
 
diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb
index 02710742625d18a20242d63b5f721a9b8c5ac73e..3ef61685398601a3f09c160fa2ac607631bb8e59 100644
--- a/spec/lib/gitlab/push_data_builder_spec.rb
+++ b/spec/lib/gitlab/push_data_builder_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'Gitlab::PushDataBuilder' do
+describe 'Gitlab::PushDataBuilder', lib: true do
   let(:project) { create(:project) }
   let(:user) { create(:user) }
 
@@ -17,9 +17,9 @@ describe 'Gitlab::PushDataBuilder' do
     it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) }
     it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) }
     it { expect(data[:total_commits_count]).to eq(3) }
-    it { expect(data[:added]).to eq(["gitlab-grack"]) }
-    it { expect(data[:modified]).to eq([".gitmodules", "files/ruby/popen.rb", "files/ruby/regex.rb"]) }
-    it { expect(data[:removed]).to eq([]) }
+    it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) }
+    it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) }
+    it { expect(data[:commits].first[:removed]).to eq([]) }
   end
 
   describe :build do
@@ -38,8 +38,5 @@ describe 'Gitlab::PushDataBuilder' do
     it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
     it { expect(data[:commits]).to be_empty }
     it { expect(data[:total_commits_count]).to be_zero }
-    it { expect(data[:added]).to eq([]) }
-    it { expect(data[:modified]).to eq([]) }
-    it { expect(data[:removed]).to eq([]) }
   end
 end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index ad84d2274e804736329c47fd087f7e91144da7c9..7d963795e17b48265d15c55ac92d4b4598716409 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::ReferenceExtractor do
+describe Gitlab::ReferenceExtractor, lib: true do
   let(:project) { create(:project) }
   subject { Gitlab::ReferenceExtractor.new(project, project.creator) }
 
@@ -97,6 +97,16 @@ describe Gitlab::ReferenceExtractor do
     expect(extracted.first.commit_to).to eq commit
   end
 
+  context 'with an external issue tracker' do
+    let(:project) { create(:jira_project) }
+    subject { described_class.new(project, project.creator) }
+
+    it 'returns JIRA issues for a JIRA-integrated project' do
+      subject.analyze('JIRA-123 and FOOBAR-4567')
+      expect(subject.issues).to eq [JiraIssue.new('JIRA-123', project), JiraIssue.new('FOOBAR-4567', project)]
+    end
+  end
+
   context 'with a project with an underscore' do
     let(:other_project) { create(:project, path: 'test_project') }
     let(:issue) { create(:issue, project: other_project) }
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 7fdc8fa600d87e8ca9a8317df6ca483f5ca1d3df..d67ee423b9baf7ad0e350758a19c93b44c0119f8 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -1,7 +1,7 @@
 # coding: utf-8
 require 'spec_helper'
 
-describe Gitlab::Regex do
+describe Gitlab::Regex, lib: true do
   describe 'project path regex' do
     it { expect('gitlab-ce').to match(Gitlab::Regex.project_path_regex) }
     it { expect('gitlab_git').to match(Gitlab::Regex.project_path_regex) }
diff --git a/spec/lib/gitlab/sherlock/collection_spec.rb b/spec/lib/gitlab/sherlock/collection_spec.rb
index a8a9d6fc7bc171f771893a605f193b11d6019140..de6bb86c5ddd8a826487e25e8742d69ffd4e0bc8 100644
--- a/spec/lib/gitlab/sherlock/collection_spec.rb
+++ b/spec/lib/gitlab/sherlock/collection_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Sherlock::Collection do
+describe Gitlab::Sherlock::Collection, lib: true do
   let(:collection) { described_class.new }
 
   let(:transaction) do
diff --git a/spec/lib/gitlab/sherlock/file_sample_spec.rb b/spec/lib/gitlab/sherlock/file_sample_spec.rb
index f05a59f56f6cb56a76ef3a74a09075861194ea49..cadf8bbce78057558264b159703a2d95517227bc 100644
--- a/spec/lib/gitlab/sherlock/file_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/file_sample_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Sherlock::FileSample do
+describe Gitlab::Sherlock::FileSample, lib: true do
   let(:sample) { described_class.new(__FILE__, [], 150.4, 2) }
 
   describe '#id' do
diff --git a/spec/lib/gitlab/sherlock/line_profiler_spec.rb b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
index 8f2e1299714ee17775b5b9f1d63d4508193a2660..d57627bba2b4ab885d793a8313067fae8e21b816 100644
--- a/spec/lib/gitlab/sherlock/line_profiler_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Sherlock::LineProfiler do
+describe Gitlab::Sherlock::LineProfiler, lib: true do
   let(:profiler) { described_class.new }
 
   describe '#profile' do
diff --git a/spec/lib/gitlab/sherlock/line_sample_spec.rb b/spec/lib/gitlab/sherlock/line_sample_spec.rb
index 5f02f6a3213eb0b998f6b7c2edc6e5b78edbd2d2..f9b61f8684e9203c7dbb9d712cdbefce41697f7f 100644
--- a/spec/lib/gitlab/sherlock/line_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_sample_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Sherlock::LineSample do
+describe Gitlab::Sherlock::LineSample, lib: true do
   let(:sample) { described_class.new(150.0, 4) }
 
   describe '#duration' do
diff --git a/spec/lib/gitlab/sherlock/location_spec.rb b/spec/lib/gitlab/sherlock/location_spec.rb
index b295a624b35e47c86e67ac37da61ddfb233f27dc..5739afa6b1e7064a0e8f77b4b511f9069d4e60fe 100644
--- a/spec/lib/gitlab/sherlock/location_spec.rb
+++ b/spec/lib/gitlab/sherlock/location_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Sherlock::Location do
+describe Gitlab::Sherlock::Location, lib: true do
   let(:location) { described_class.new(__FILE__, 1) }
 
   describe 'from_ruby_location' do
diff --git a/spec/lib/gitlab/sherlock/middleware_spec.rb b/spec/lib/gitlab/sherlock/middleware_spec.rb
index aa74fc53a7954dd6a2413eab494ec4eef64a8d25..2bbeb25ce98341bbaa68d16b9615f9e89c557ac4 100644
--- a/spec/lib/gitlab/sherlock/middleware_spec.rb
+++ b/spec/lib/gitlab/sherlock/middleware_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Sherlock::Middleware do
+describe Gitlab::Sherlock::Middleware, lib: true do
   let(:app) { double(:app) }
   let(:middleware) { described_class.new(app) }
 
diff --git a/spec/lib/gitlab/sherlock/query_spec.rb b/spec/lib/gitlab/sherlock/query_spec.rb
index a9afef5dc1dc8a7ac579bb7eead3ac44d47b320e..05da915ccfd827cc693f2512cad2aa1980a0ca5a 100644
--- a/spec/lib/gitlab/sherlock/query_spec.rb
+++ b/spec/lib/gitlab/sherlock/query_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Sherlock::Query do
+describe Gitlab::Sherlock::Query, lib: true do
   let(:started_at)  { Time.utc(2015, 1, 1) }
   let(:finished_at) { started_at + 5 }
 
diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb
index bb49fb65cf8c6ec65bcb5f8a02abe5b17aed2f2b..7553f2a045fc6cdd7a1a8b990b90c07b3e748e81 100644
--- a/spec/lib/gitlab/sherlock/transaction_spec.rb
+++ b/spec/lib/gitlab/sherlock/transaction_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Sherlock::Transaction do
+describe Gitlab::Sherlock::Transaction, lib: true do
   let(:transaction) { described_class.new('POST', '/cat_pictures') }
 
   describe '#id' do
@@ -84,6 +84,19 @@ describe Gitlab::Sherlock::Transaction do
     end
   end
 
+  describe '#query_duration' do
+    it 'returns the total query duration in seconds' do
+      time   = Time.now
+      query1 = Gitlab::Sherlock::Query.new('SELECT 1', time, time + 5)
+      query2 = Gitlab::Sherlock::Query.new('SELECT 2', time, time + 2)
+
+      transaction.queries << query1
+      transaction.queries << query2
+
+      expect(transaction.query_duration).to be_within(0.1).of(7.0)
+    end
+  end
+
   describe '#to_param' do
     it 'returns the transaction ID' do
       expect(transaction.to_param).to eq(transaction.id)
diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb
index 9e1cd4419e0b00bdc904f3279db285706a4ae07c..0cdbab8754417a4a5d4494718186372f7d42d801 100644
--- a/spec/lib/gitlab/sql/union_spec.rb
+++ b/spec/lib/gitlab/sql/union_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::SQL::Union do
+describe Gitlab::SQL::Union, lib: true do
   describe '#to_sql' do
     it 'returns a String joining relations together using a UNION' do
       rel1  = User.where(email: 'alice@example.com')
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
index e554458e41ca55fc6c5e3349879812e3a1a1f0b5..7a140518dd2448f348fa46907157d7caab6da44c 100644
--- a/spec/lib/gitlab/themes_spec.rb
+++ b/spec/lib/gitlab/themes_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Themes do
+describe Gitlab::Themes, lib: true do
   describe '.body_classes' do
     it 'returns a space-separated list of class names' do
       css = described_class.body_classes
diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb
index 8df84665e166a6cc18eda8f75bf8d0d83a7ae78e..e958e087a80ff0a8a087c7f64877c285c7498421 100644
--- a/spec/lib/gitlab/upgrader_spec.rb
+++ b/spec/lib/gitlab/upgrader_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::Upgrader do
+describe Gitlab::Upgrader, lib: true do
   let(:upgrader) { Gitlab::Upgrader.new }
   let(:current_version) { Gitlab::VERSION }
 
diff --git a/spec/lib/gitlab/uploads_transfer_spec.rb b/spec/lib/gitlab/uploads_transfer_spec.rb
index 260364a513e4d2646213999645cd09511ba0b85d..4092f7fb638e19f732234be4324ab847b374222e 100644
--- a/spec/lib/gitlab/uploads_transfer_spec.rb
+++ b/spec/lib/gitlab/uploads_transfer_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::UploadsTransfer do
+describe Gitlab::UploadsTransfer, lib: true do
   before do
     @root_dir = File.join(Rails.root, "public", "uploads")
     @upload_transfer = Gitlab::UploadsTransfer.new
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 5153ed15af3f865fed83d30545d9d99a82685639..f023be6ae45dd3e7374a5a70d6e4703eafb17f1c 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Gitlab::UrlBuilder do
+describe Gitlab::UrlBuilder, lib: true do
   describe 'When asking for an issue' do
     it 'returns the issue url' do
       issue = create(:issue)
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index 18f71b40fe093cdb7c5e766fc0e126809e31a425..706ee9bec5809128ded7b44cfaad9282489affa9 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'Gitlab::VersionInfo', no_db: true do
+describe 'Gitlab::VersionInfo', lib: true, no_db: true do
   before do
     @unknown = Gitlab::VersionInfo.new
     @v0_0_1 = Gitlab::VersionInfo.new(0, 0, 1)
diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb
index 37240d5131087c81176b3781d962e6ecca4699cb..63b5292b098e91034e6ace12cdea9b7fdd1bf911 100644
--- a/spec/lib/repository_cache_spec.rb
+++ b/spec/lib/repository_cache_spec.rb
@@ -1,6 +1,6 @@
 require_relative '../../lib/repository_cache'
 
-describe RepositoryCache do
+describe RepositoryCache, lib: true do
   let(:backend) { double('backend').as_null_object }
   let(:cache) { RepositoryCache.new('example', backend) }
 
diff --git a/spec/mailers/ci/notify_spec.rb b/spec/mailers/ci/notify_spec.rb
deleted file mode 100644
index b83fb41603bff1a5cb212e0d8387070806747f44..0000000000000000000000000000000000000000
--- a/spec/mailers/ci/notify_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'spec_helper'
-
-describe Ci::Notify do
-  include EmailSpec::Helpers
-  include EmailSpec::Matchers
-
-  before do
-    @commit = FactoryGirl.create :ci_commit
-    @build = FactoryGirl.create :ci_build, commit: @commit
-  end
-
-  describe 'build success' do
-    subject { Ci::Notify.build_success_email(@build.id, 'wow@example.com') }
-
-    it 'has the correct subject' do
-      should have_subject /Build success for/
-    end
-
-    it 'contains name of project' do
-      should have_body_text /build successful/
-    end
-  end
-
-  describe 'build fail' do
-    subject { Ci::Notify.build_fail_email(@build.id, 'wow@example.com') }
-
-    it 'has the correct subject' do
-      should have_subject /Build failed for/
-    end
-
-    it 'contains name of project' do
-      should have_body_text /build failed/
-    end
-  end
-end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 47863d54579e05959f2c5930d89bf2352d8f695e..154901a2fbcd44b91332fee867a2ff68fa05e0a8 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -13,6 +13,7 @@ describe Notify do
   let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
   let(:recipient) { create(:user, email: 'recipient@example.com') }
   let(:project) { create(:project) }
+  let(:build) { create(:ci_build) }
 
   before(:each) do
     ActionMailer::Base.deliveries.clear
@@ -77,6 +78,32 @@ describe Notify do
     end
   end
 
+  shared_examples 'it should have Gmail Actions links' do
+    it { is_expected.to have_body_text /ViewAction/ }
+  end
+
+  shared_examples 'it should not have Gmail Actions links' do
+    it { is_expected.to_not have_body_text /ViewAction/ }
+  end
+
+  shared_examples 'it should show Gmail Actions View Issue link' do
+    it_behaves_like 'it should have Gmail Actions links'
+
+    it { is_expected.to have_body_text /View Issue/ }
+  end
+
+  shared_examples 'it should show Gmail Actions View Merge request link' do
+    it_behaves_like 'it should have Gmail Actions links'
+
+    it { is_expected.to have_body_text /View Merge request/ }
+  end
+
+  shared_examples 'it should show Gmail Actions View Commit link' do
+    it_behaves_like 'it should have Gmail Actions links'
+
+    it { is_expected.to have_body_text /View Commit/ }
+  end
+
   describe 'for new users, the email' do
     let(:example_site_path) { root_path }
     let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
@@ -87,6 +114,7 @@ describe Notify do
 
     it_behaves_like 'an email sent from GitLab'
     it_behaves_like 'a new user email', new_user_address
+    it_behaves_like 'it should not have Gmail Actions links'
 
     it 'contains the password text' do
       is_expected.to have_body_text /Click here to set your password/
@@ -115,6 +143,7 @@ describe Notify do
 
     it_behaves_like 'an email sent from GitLab'
     it_behaves_like 'a new user email', new_user_address
+    it_behaves_like 'it should not have Gmail Actions links'
 
     it 'should not contain the new user\'s password' do
       is_expected.not_to have_body_text /password/
@@ -127,6 +156,7 @@ describe Notify do
     subject { Notify.new_ssh_key_email(key.id) }
 
     it_behaves_like 'an email sent from GitLab'
+    it_behaves_like 'it should not have Gmail Actions links'
 
     it 'is sent to the new user' do
       is_expected.to deliver_to key.user.email
@@ -150,6 +180,8 @@ describe Notify do
 
     subject { Notify.new_email_email(email.id) }
 
+    it_behaves_like 'it should not have Gmail Actions links'
+
     it 'is sent to the new user' do
       is_expected.to deliver_to email.user.email
     end
@@ -194,6 +226,7 @@ describe Notify do
 
           it_behaves_like 'an assignee email'
           it_behaves_like 'an email starting a new thread', 'issue'
+          it_behaves_like 'it should show Gmail Actions View Issue link'
 
           it 'has the correct subject' do
             is_expected.to have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/
@@ -207,16 +240,19 @@ describe Notify do
         describe 'that are new with a description' do
           subject { Notify.new_issue_email(issue_with_description.assignee_id, issue_with_description.id) }
 
+          it_behaves_like 'it should show Gmail Actions View Issue link'
+
           it 'contains the description' do
             is_expected.to have_body_text /#{issue_with_description.description}/
           end
         end
 
         describe 'that have been reassigned' do
-          subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user) }
+          subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user.id) }
 
           it_behaves_like 'a multiple recipients email'
           it_behaves_like 'an answer to an existing thread', 'issue'
+          it_behaves_like 'it should show Gmail Actions View Issue link'
 
           it 'is sent as the author' do
             sender = subject.header[:from].addrs[0]
@@ -243,9 +279,10 @@ describe Notify do
 
         describe 'status changed' do
           let(:status) { 'closed' }
-          subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) }
+          subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) }
 
           it_behaves_like 'an answer to an existing thread', 'issue'
+          it_behaves_like 'it should show Gmail Actions View Issue link'
 
           it 'is sent as the author' do
             sender = subject.header[:from].addrs[0]
@@ -269,7 +306,6 @@ describe Notify do
             is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
           end
         end
-
       end
 
       context 'for merge requests' do
@@ -282,6 +318,7 @@ describe Notify do
 
           it_behaves_like 'an assignee email'
           it_behaves_like 'an email starting a new thread', 'merge_request'
+          it_behaves_like 'it should show Gmail Actions View Merge request link'
 
           it 'has the correct subject' do
             is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
@@ -307,6 +344,8 @@ describe Notify do
         describe 'that are new with a description' do
           subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) }
 
+          it_behaves_like 'it should show Gmail Actions View Merge request link'
+
           it 'contains the description' do
             is_expected.to have_body_text /#{merge_request_with_description.description}/
           end
@@ -317,6 +356,7 @@ describe Notify do
 
           it_behaves_like 'a multiple recipients email'
           it_behaves_like 'an answer to an existing thread', 'merge_request'
+          it_behaves_like 'it should show Gmail Actions View Merge request link'
 
           it 'is sent as the author' do
             sender = subject.header[:from].addrs[0]
@@ -343,9 +383,10 @@ describe Notify do
 
         describe 'status changed' do
           let(:status) { 'reopened' }
-          subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user) }
+          subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) }
 
           it_behaves_like 'an answer to an existing thread', 'merge_request'
+          it_behaves_like 'it should show Gmail Actions View Merge request link'
 
           it 'is sent as the author' do
             sender = subject.header[:from].addrs[0]
@@ -375,6 +416,7 @@ describe Notify do
 
           it_behaves_like 'a multiple recipients email'
           it_behaves_like 'an answer to an existing thread', 'merge_request'
+          it_behaves_like 'it should show Gmail Actions View Merge request link'
 
           it 'is sent as the merge author' do
             sender = subject.header[:from].addrs[0]
@@ -403,6 +445,7 @@ describe Notify do
       subject { Notify.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
 
       it_behaves_like 'an email sent from GitLab'
+      it_behaves_like 'it should not have Gmail Actions links'
 
       it 'has the correct subject' do
         is_expected.to have_subject /Project was moved/
@@ -424,13 +467,16 @@ describe Notify do
       subject { Notify.project_access_granted_email(project_member.id) }
 
       it_behaves_like 'an email sent from GitLab'
+      it_behaves_like 'it should not have Gmail Actions links'
 
       it 'has the correct subject' do
         is_expected.to have_subject /Access to project was granted/
       end
+
       it 'contains name of project' do
         is_expected.to have_body_text /#{project.name}/
       end
+
       it 'contains new user role' do
         is_expected.to have_body_text /#{project_member.human_access}/
       end
@@ -445,6 +491,8 @@ describe Notify do
       end
 
       shared_examples 'a note email' do
+        it_behaves_like 'it should have Gmail Actions links'
+
         it 'is sent as the author' do
           sender = subject.header[:from].addrs[0]
           expect(sender.display_name).to eq(note_author.name)
@@ -469,6 +517,7 @@ describe Notify do
 
         it_behaves_like 'a note email'
         it_behaves_like 'an answer to an existing thread', 'commit'
+        it_behaves_like 'it should show Gmail Actions View Commit link'
 
         it 'has the correct subject' do
           is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/
@@ -488,6 +537,7 @@ describe Notify do
 
         it_behaves_like 'a note email'
         it_behaves_like 'an answer to an existing thread', 'merge_request'
+        it_behaves_like 'it should show Gmail Actions View Merge request link'
 
         it 'has the correct subject' do
           is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
@@ -507,6 +557,7 @@ describe Notify do
 
         it_behaves_like 'a note email'
         it_behaves_like 'an answer to an existing thread', 'issue'
+        it_behaves_like 'it should show Gmail Actions View Issue link'
 
         it 'has the correct subject' do
           is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/
@@ -527,6 +578,7 @@ describe Notify do
     subject { Notify.group_access_granted_email(membership.id) }
 
     it_behaves_like 'an email sent from GitLab'
+    it_behaves_like 'it should not have Gmail Actions links'
 
     it 'has the correct subject' do
       is_expected.to have_subject /Access to group was granted/
@@ -546,8 +598,10 @@ describe Notify do
     let(:user) { create(:user, email: 'old-email@mail.com') }
 
     before do
-      user.email = "new-email@mail.com"
-      user.save
+      perform_enqueued_jobs do
+        user.email = "new-email@mail.com"
+        user.save
+      end
     end
 
     subject { ActionMailer::Base.deliveries.last }
@@ -574,6 +628,8 @@ describe Notify do
 
     subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :create) }
 
+    it_behaves_like 'it should not have Gmail Actions links'
+
     it 'is sent as the author' do
       sender = subject.header[:from].addrs[0]
       expect(sender.display_name).to eq(user.name)
@@ -600,6 +656,8 @@ describe Notify do
 
     subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
 
+    it_behaves_like 'it should not have Gmail Actions links'
+
     it 'is sent as the author' do
       sender = subject.header[:from].addrs[0]
       expect(sender.display_name).to eq(user.name)
@@ -625,6 +683,8 @@ describe Notify do
 
     subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :delete) }
 
+    it_behaves_like 'it should not have Gmail Actions links'
+
     it 'is sent as the author' do
       sender = subject.header[:from].addrs[0]
       expect(sender.display_name).to eq(user.name)
@@ -646,6 +706,8 @@ describe Notify do
 
     subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) }
 
+    it_behaves_like 'it should not have Gmail Actions links'
+
     it 'is sent as the author' do
       sender = subject.header[:from].addrs[0]
       expect(sender.display_name).to eq(user.name)
@@ -671,6 +733,8 @@ describe Notify do
 
     subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, send_from_committer_email: send_from_committer_email) }
 
+    it_behaves_like 'it should not have Gmail Actions links'
+
     it 'is sent as the author' do
       sender = subject.header[:from].addrs[0]
       expect(sender.display_name).to eq(user.name)
@@ -774,6 +838,8 @@ describe Notify do
 
     subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) }
 
+    it_behaves_like 'it should show Gmail Actions View Commit link'
+
     it 'is sent as the author' do
       sender = subject.header[:from].addrs[0]
       expect(sender.display_name).to eq(user.name)
@@ -800,4 +866,32 @@ describe Notify do
       is_expected.to have_body_text /#{diff_path}/
     end
   end
+
+  describe 'build success' do
+    before { build.success }
+
+    subject { Notify.build_success_email(build.id, 'wow@example.com') }
+
+    it 'has the correct subject' do
+      should have_subject /Build success for/
+    end
+
+    it 'contains name of project' do
+      should have_body_text build.project_name
+    end
+  end
+
+  describe 'build fail' do
+    before { build.drop }
+
+    subject { Notify.build_fail_email(build.id, 'wow@example.com') }
+
+    it 'has the correct subject' do
+      should have_subject /Build failed for/
+    end
+
+    it 'contains name of project' do
+      should have_body_text build.project_name
+    end
+  end
 end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index dfbac7b4004600c782713ec5f115f385c1617eb3..35d8220ae548ebce18ae2547f72352eada10e5f8 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -27,6 +27,7 @@
 #  admin_notification_email     :string(255)
 #  shared_runners_enabled       :boolean          default(TRUE), not null
 #  max_artifacts_size           :integer          default(100), not null
+#  runners_registration_token   :string(255)
 #
 
 require 'spec_helper'
@@ -36,6 +37,22 @@ describe ApplicationSetting, models: true do
 
   it { expect(setting).to be_valid }
 
+  describe 'validations' do
+    let(:http)  { 'http://example.com' }
+    let(:https) { 'https://example.com' }
+    let(:ftp)   { 'ftp://example.com' }
+
+    it { is_expected.to allow_value(nil).for(:home_page_url) }
+    it { is_expected.to allow_value(http).for(:home_page_url) }
+    it { is_expected.to allow_value(https).for(:home_page_url) }
+    it { is_expected.not_to allow_value(ftp).for(:home_page_url) }
+
+    it { is_expected.to allow_value(nil).for(:after_sign_out_path) }
+    it { is_expected.to allow_value(http).for(:after_sign_out_path) }
+    it { is_expected.to allow_value(https).for(:after_sign_out_path) }
+    it { is_expected.not_to allow_value(ftp).for(:after_sign_out_path) }
+  end
+
   context 'restricted signup domains' do
     it 'set single domain' do
       setting.restricted_signup_domains_raw = 'example.com'
@@ -57,26 +74,4 @@ describe ApplicationSetting, models: true do
       expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com'])
     end
   end
-
-  context 'shared runners' do
-    let(:gl_project) { create(:empty_project) }
-
-    before do
-      allow_any_instance_of(Project).to receive(:current_application_settings).and_return(setting)
-    end
-
-    subject { gl_project.ensure_gitlab_ci_project.shared_runners_enabled }
-
-    context 'enabled' do
-      before { setting.update_attributes(shared_runners_enabled: true) }
-
-      it { is_expected.to be_truthy }
-    end
-
-    context 'disabled' do
-      before { setting.update_attributes(shared_runners_enabled: false) }
-
-      it { is_expected.to be_falsey }
-    end
-  end
 end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index d80748f23a41f74baf8f07c83304f9b4d6bcda63..e4cac105110ed86113df1bcb0e6dbfc355744f96 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -15,11 +15,26 @@
 
 require 'spec_helper'
 
-describe BroadcastMessage do
+describe BroadcastMessage, models: true do
   subject { create(:broadcast_message) }
 
   it { is_expected.to be_valid }
 
+  describe 'validations' do
+    let(:triplet) { '#000' }
+    let(:hex)     { '#AABBCC' }
+
+    it { is_expected.to allow_value(nil).for(:color) }
+    it { is_expected.to allow_value(triplet).for(:color) }
+    it { is_expected.to allow_value(hex).for(:color) }
+    it { is_expected.not_to allow_value('000').for(:color) }
+
+    it { is_expected.to allow_value(nil).for(:font) }
+    it { is_expected.to allow_value(triplet).for(:font) }
+    it { is_expected.to allow_value(hex).for(:font) }
+    it { is_expected.not_to allow_value('000').for(:font) }
+  end
+
   describe :current do
     it "should return last message if time match" do
       broadcast_message = create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow)
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 839b4c6b16ea93502329d6704f91c2e02c3628f6..1c22e3cb7c405363d30a575ce90178341f345da8 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -25,10 +25,9 @@
 
 require 'spec_helper'
 
-describe Ci::Build do
-  let(:project) { FactoryGirl.create :ci_project }
-  let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
-  let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
+describe Ci::Build, models: true do
+  let(:project) { FactoryGirl.create :empty_project }
+  let(:commit) { FactoryGirl.create :ci_commit, project: project }
   let(:build) { FactoryGirl.create :ci_build, commit: commit }
 
   it { is_expected.to validate_presence_of :ref }
@@ -112,7 +111,7 @@ describe Ci::Build do
       let(:token) { 'my_secret_token' }
 
       before do
-        build.project.update_attributes(token: token)
+        build.project.update_attributes(runners_token: token)
         build.update_attributes(trace: token)
       end
 
@@ -120,11 +119,12 @@ describe Ci::Build do
     end
   end
 
-  describe :timeout do
-    subject { build.timeout }
-
-    it { is_expected.to eq(commit.project.timeout) }
-  end
+  # TODO: build timeout
+  # describe :timeout do
+  #   subject { build.timeout }
+  #
+  #   it { is_expected.to eq(commit.project.timeout) }
+  # end
 
   describe :options do
     let(:options) do
@@ -140,11 +140,12 @@ describe Ci::Build do
     it { is_expected.to eq(options) }
   end
 
-  describe :allow_git_fetch do
-    subject { build.allow_git_fetch }
-
-    it { is_expected.to eq(project.allow_git_fetch) }
-  end
+  # TODO: allow_git_fetch
+  # describe :allow_git_fetch do
+  #   subject { build.allow_git_fetch }
+  #
+  #   it { is_expected.to eq(project.allow_git_fetch) }
+  # end
 
   describe :project do
     subject { build.project }
@@ -164,12 +165,6 @@ describe Ci::Build do
     it { is_expected.to eq(project.name) }
   end
 
-  describe :repo_url do
-    subject { build.repo_url }
-
-    it { is_expected.to eq(project.repo_url_with_auth) }
-  end
-
   describe :extract_coverage do
     context 'valid content & regex' do
       subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') }
@@ -194,6 +189,12 @@ describe Ci::Build do
 
       it { is_expected.to eq(98.29) }
     end
+
+    context 'using a regex capture' do
+      subject { build.extract_coverage('TOTAL      9926   3489    65%', 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)') }
+
+      it { is_expected.to eq(65) }
+    end
   end
 
   describe :variables do
@@ -266,40 +267,6 @@ describe Ci::Build do
     end
   end
 
-  describe :project_recipients do
-    let(:pusher_email) { 'pusher@gitlab.test' }
-    let(:user) { User.new(notification_email: pusher_email) }
-    subject { build.project_recipients }
-
-    before do
-      build.update_attributes(user: user)
-    end
-
-    it 'should return pusher_email as only recipient when no additional recipients are given' do
-      project.update_attributes(email_add_pusher: true,
-                                email_recipients: '')
-      is_expected.to eq([pusher_email])
-    end
-
-    it 'should return pusher_email and additional recipients' do
-      project.update_attributes(email_add_pusher: true,
-                                email_recipients: 'rec1 rec2')
-      is_expected.to eq(['rec1', 'rec2', pusher_email])
-    end
-
-    it 'should return recipients' do
-      project.update_attributes(email_add_pusher: false,
-                                email_recipients: 'rec1 rec2')
-      is_expected.to eq(['rec1', 'rec2'])
-    end
-
-    it 'should return unique recipients only' do
-      project.update_attributes(email_add_pusher: true,
-                                email_recipients: "rec1 rec1 #{pusher_email}")
-      is_expected.to eq(['rec1', pusher_email])
-    end
-  end
-
   describe :can_be_served? do
     let(:runner) { FactoryGirl.create :ci_specific_runner }
 
@@ -415,4 +382,82 @@ describe Ci::Build do
       is_expected.to_not be_nil
     end
   end
+
+  describe :repo_url do
+    let(:build) { FactoryGirl.create :ci_build }
+    let(:project) { build.project }
+
+    subject { build.repo_url }
+
+    it { is_expected.to be_a(String) }
+    it { is_expected.to end_with(".git") }
+    it { is_expected.to start_with(project.web_url[0..6]) }
+    it { is_expected.to include(build.token) }
+    it { is_expected.to include('gitlab-ci-token') }
+    it { is_expected.to include(project.web_url[7..-1]) }
+  end
+
+  def create_mr(build, commit, factory: :merge_request, created_at: Time.now)
+    FactoryGirl.create(factory,
+                       source_project_id: commit.gl_project_id,
+                       target_project_id: commit.gl_project_id,
+                       source_branch: build.ref,
+                       created_at: created_at)
+  end
+
+  describe :merge_request do
+    context 'when a MR has a reference to the commit' do
+      before do
+        @merge_request = create_mr(build, commit, factory: :merge_request)
+
+        commits = [double(id: commit.sha)]
+        allow(@merge_request).to receive(:commits).and_return(commits)
+        allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
+      end
+
+      it 'returns the single associated MR' do
+        expect(build.merge_request.id).to eq(@merge_request.id)
+      end
+    end
+
+    context 'when there is not a MR referencing the commit' do
+      it 'returns nil' do
+        expect(build.merge_request).to be_nil
+      end
+    end
+
+    context 'when more than one MR have a reference to the commit' do
+      before do
+        @merge_request = create_mr(build, commit, factory: :merge_request)
+        @merge_request.close!
+        @merge_request2 = create_mr(build, commit, factory: :merge_request)
+
+        commits = [double(id: commit.sha)]
+        allow(@merge_request).to receive(:commits).and_return(commits)
+        allow(@merge_request2).to receive(:commits).and_return(commits)
+        allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request, @merge_request2])
+      end
+
+      it 'returns the first MR' do
+        expect(build.merge_request.id).to eq(@merge_request.id)
+      end
+    end
+
+    context 'when a Build is created after the MR' do
+      before do
+        @merge_request = create_mr(build, commit, factory: :merge_request_with_diffs)
+        commit2 = FactoryGirl.create :ci_commit, project: project
+        @build2 = FactoryGirl.create :ci_build, commit: commit2
+
+        commits = [double(id: commit.sha), double(id: commit2.sha)]
+        allow(@merge_request).to receive(:commits).and_return(commits)
+        allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
+      end
+
+      it 'returns the current MR' do
+        expect(@build2.merge_request.id).to eq(@merge_request.id)
+      end
+    end
+
+  end
 end
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
index a13f6458cac7bfb960893214986642518247dca4..b193e16e7f8dfee872048fe37dc65f62e537de2e 100644
--- a/spec/models/ci/commit_spec.rb
+++ b/spec/models/ci/commit_spec.rb
@@ -13,17 +13,16 @@
 #  tag           :boolean          default(FALSE)
 #  yaml_errors   :text
 #  committed_at  :datetime
-#  gl_project_id :integer
+#  project_id :integer
 #
 
 require 'spec_helper'
 
-describe Ci::Commit do
-  let(:project) { FactoryGirl.create :ci_project }
-  let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
-  let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
+describe Ci::Commit, models: true do
+  let(:project) { FactoryGirl.create :empty_project }
+  let(:commit) { FactoryGirl.create :ci_commit, project: project }
 
-  it { is_expected.to belong_to(:gl_project) }
+  it { is_expected.to belong_to(:project) }
   it { is_expected.to have_many(:statuses) }
   it { is_expected.to have_many(:trigger_requests) }
   it { is_expected.to have_many(:builds) }
@@ -37,16 +36,16 @@ describe Ci::Commit do
     let(:project) { FactoryGirl.create :empty_project }
 
     it 'returns ordered list of commits' do
-      commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project
-      commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project
+      commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
+      commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hours.ago, project: project
       expect(project.ci_commits.ordered).to eq([commit2, commit1])
     end
 
     it 'returns commits ordered by committed_at and id, with nulls last' do
-      commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project
-      commit2 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project
-      commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project
-      commit4 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project
+      commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
+      commit2 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
+      commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hours.ago, project: project
+      commit4 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
       expect(project.ci_commits.ordered).to eq([commit2, commit4, commit3, commit1])
     end
   end
@@ -162,7 +161,7 @@ describe Ci::Commit do
   end
 
   describe :create_builds do
-    let!(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
+    let!(:commit) { FactoryGirl.create :ci_commit, project: project }
 
     def create_builds(trigger_request = nil)
       commit.create_builds('master', false, nil, trigger_request)
@@ -390,9 +389,8 @@ describe Ci::Commit do
   end
 
   describe "coverage" do
-    let(:project) { FactoryGirl.create :ci_project, coverage_regex: "/.*/" }
-    let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
-    let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
+    let(:project) { FactoryGirl.create :empty_project, build_coverage_regex: "/.*/" }
+    let(:commit) { FactoryGirl.create :ci_commit, project: project }
 
     it "calculates average when there are two builds with coverage" do
       FactoryGirl.create :ci_build, name: "rspec", coverage: 30, commit: commit
diff --git a/spec/models/ci/project_services/hip_chat_message_spec.rb b/spec/models/ci/project_services/hip_chat_message_spec.rb
deleted file mode 100644
index e23d6ae2c2844ae7e090011910096f32ba5aa44b..0000000000000000000000000000000000000000
--- a/spec/models/ci/project_services/hip_chat_message_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'spec_helper'
-
-describe Ci::HipChatMessage do
-  subject { Ci::HipChatMessage.new(build) }
-
-  let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs) }
-
-  let(:build) do
-    commit.builds.first
-  end
-
-  context 'when all matrix builds succeed' do
-    it 'returns a successful message' do
-      commit.create_builds('master', false, nil)
-      commit.builds.update_all(status: "success")
-      commit.reload
-
-      expect(subject.status_color).to eq 'green'
-      expect(subject.notify?).to be_falsey
-      expect(subject.to_s).to match(/Commit #\d+/)
-      expect(subject.to_s).to match(/Successful in \d+ second\(s\)\./)
-    end
-  end
-
-  context 'when at least one matrix build fails' do
-    it 'returns a failure message' do
-      commit.create_builds('master', false, nil)
-      first_build = commit.builds.first
-      second_build = commit.builds.last
-      first_build.update(status: "success")
-      second_build.update(status: "failed")
-
-      expect(subject.status_color).to eq 'red'
-      expect(subject.notify?).to be_truthy
-      expect(subject.to_s).to match(/Commit #\d+/)
-      expect(subject.to_s).to match(/Failed in \d+ second\(s\)\./)
-    end
-  end
-end
diff --git a/spec/models/ci/project_services/hip_chat_service_spec.rb b/spec/models/ci/project_services/hip_chat_service_spec.rb
deleted file mode 100644
index d9ccc855edf57283989b104850192d6df976cf79..0000000000000000000000000000000000000000
--- a/spec/models/ci/project_services/hip_chat_service_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id         :integer          not null, primary key
-#  type       :string(255)
-#  title      :string(255)
-#  project_id :integer          not null
-#  created_at :datetime
-#  updated_at :datetime
-#  active     :boolean          default(FALSE), not null
-#  properties :text
-#
-
-
-require 'spec_helper'
-
-describe Ci::HipChatService do
-
-  describe "Validations" do
-
-    context "active" do
-      before do
-        subject.active = true
-      end
-
-      it { is_expected.to validate_presence_of :hipchat_room }
-      it { is_expected.to validate_presence_of :hipchat_token }
-
-    end
-  end
-
-  describe "Execute" do
-
-    let(:service) { Ci::HipChatService.new }
-    let(:commit)  { FactoryGirl.create :ci_commit }
-    let(:build)   { FactoryGirl.create :ci_build, commit: commit, status: 'failed' }
-    let(:api_url) { 'https://api.hipchat.com/v2/room/123/notification?auth_token=a1b2c3d4e5f6' }
-
-    before do
-      allow(service).to receive_messages(
-        project: commit.project,
-        project_id: commit.project_id,
-        notify_only_broken_builds: false,
-        hipchat_room: 123,
-        hipchat_token: 'a1b2c3d4e5f6'
-      )
-
-      WebMock.stub_request(:post, api_url)
-    end
-
-
-    it "should call the HipChat API" do
-      service.execute(build)
-      Ci::HipChatNotifierWorker.drain
-
-      expect( WebMock ).to have_requested(:post, api_url).once
-    end
-
-    it "calls the worker with expected arguments" do
-      expect( Ci::HipChatNotifierWorker ).to receive(:perform_async) \
-        .with(an_instance_of(String), hash_including(
-          token: 'a1b2c3d4e5f6',
-          room: 123,
-          server: 'https://api.hipchat.com',
-          color: 'red',
-          notify: true
-        ))
-
-      service.execute(build)
-    end
-  end
-end
diff --git a/spec/models/ci/project_services/mail_service_spec.rb b/spec/models/ci/project_services/mail_service_spec.rb
deleted file mode 100644
index d9b3d34ff1529bd04343f399b4b2fef6aac60a35..0000000000000000000000000000000000000000
--- a/spec/models/ci/project_services/mail_service_spec.rb
+++ /dev/null
@@ -1,191 +0,0 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id         :integer          not null, primary key
-#  type       :string(255)
-#  title      :string(255)
-#  project_id :integer          not null
-#  created_at :datetime
-#  updated_at :datetime
-#  active     :boolean          default(FALSE), not null
-#  properties :text
-#
-
-require 'spec_helper'
-
-describe Ci::MailService do
-  describe "Associations" do
-    it { is_expected.to belong_to :project }
-  end
-
-  describe "Validations" do
-    context "active" do
-      before do
-        subject.active = true
-      end
-    end
-  end
-
-  describe 'Sends email for' do
-    let(:mail)   { Ci::MailService.new }
-    let(:user)   { User.new(notification_email: 'git@example.com')}
-
-    describe 'failed build' do
-      let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true) }
-      let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
-      let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
-      let(:build) { FactoryGirl.create(:ci_build, status: 'failed', commit: commit, user: user) }
-
-      before do
-        allow(mail).to receive_messages(
-          project: project
-        )
-      end
-
-      it do
-        should_email("git@example.com")
-        mail.execute(build)
-      end
-
-      def should_email(email)
-        expect(Ci::Notify).to receive(:build_fail_email).with(build.id, email)
-        expect(Ci::Notify).not_to receive(:build_success_email).with(build.id, email)
-      end
-    end
-
-    describe 'successfull build' do
-      let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true, email_only_broken_builds: false) }
-      let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
-      let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
-      let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
-
-      before do
-        allow(mail).to receive_messages(
-          project: project
-        )
-      end
-
-      it do
-        should_email("git@example.com")
-        mail.execute(build)
-      end
-
-      def should_email(email)
-        expect(Ci::Notify).to receive(:build_success_email).with(build.id, email)
-        expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email)
-      end
-    end
-
-    describe 'successfull build and project has email_recipients' do
-      let(:project) do
-        FactoryGirl.create(:ci_project,
-                           email_add_pusher: true,
-                           email_only_broken_builds: false,
-                           email_recipients: "jeroen@example.com")
-      end
-      let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
-      let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
-      let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
-
-      before do
-        allow(mail).to receive_messages(
-          project: project
-        )
-      end
-
-      it do
-        should_email("git@example.com")
-        should_email("jeroen@example.com")
-        mail.execute(build)
-      end
-
-      def should_email(email)
-        expect(Ci::Notify).to receive(:build_success_email).with(build.id, email)
-        expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email)
-      end
-    end
-
-    describe 'successful build and notify only broken builds' do
-      let(:project) do
-        FactoryGirl.create(:ci_project,
-                           email_add_pusher: true,
-                           email_only_broken_builds: true,
-                           email_recipients: "jeroen@example.com")
-      end
-      let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
-      let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
-      let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
-
-      before do
-        allow(mail).to receive_messages(
-          project: project
-        )
-      end
-
-      it do
-        should_email(commit.git_author_email)
-        should_email("jeroen@example.com")
-        mail.execute(build) if mail.can_execute?(build)
-      end
-
-      def should_email(email)
-        expect(Ci::Notify).not_to receive(:build_success_email).with(build.id, email)
-        expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email)
-      end
-    end
-
-    describe 'successful build and can test service' do
-      let(:project) do
-        FactoryGirl.create(:ci_project,
-                           email_add_pusher: true,
-                           email_only_broken_builds: false,
-                           email_recipients: "jeroen@example.com")
-      end
-      let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
-      let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
-      let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
-
-      before do
-        allow(mail).to receive_messages(
-          project: project
-        )
-        build
-      end
-
-      it do
-        expect(mail.can_test?).to eq(true)
-      end
-    end
-
-    describe 'retried build should not receive email' do
-      let(:project) do
-        FactoryGirl.create(:ci_project,
-                           email_add_pusher: true,
-                           email_only_broken_builds: true,
-                           email_recipients: "jeroen@example.com")
-      end
-      let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
-      let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
-      let(:build) { FactoryGirl.create(:ci_build, status: 'failed', commit: commit, user: user) }
-
-      before do
-        allow(mail).to receive_messages(
-          project: project
-        )
-      end
-
-      it do
-        Ci::Build.retry(build)
-        should_email(commit.git_author_email)
-        should_email("jeroen@example.com")
-        mail.execute(build) if mail.can_execute?(build)
-      end
-
-      def should_email(email)
-        expect(Ci::Notify).not_to receive(:build_success_email).with(build.id, email)
-        expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email)
-      end
-    end
-  end
-end
diff --git a/spec/models/ci/project_services/slack_message_spec.rb b/spec/models/ci/project_services/slack_message_spec.rb
deleted file mode 100644
index 8adda6c86ccc84950f35290142b0e59dae010e43..0000000000000000000000000000000000000000
--- a/spec/models/ci/project_services/slack_message_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'spec_helper'
-
-describe Ci::SlackMessage do
-  subject { Ci::SlackMessage.new(commit) }
-
-  let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs) }
-
-  context 'when all matrix builds succeeded' do
-    let(:color) { 'good' }
-
-    it 'returns a message with success' do
-      commit.create_builds('master', false, nil)
-      commit.builds.update_all(status: "success")
-      commit.reload
-
-      expect(subject.color).to eq(color)
-      expect(subject.fallback).to include('Commit')
-      expect(subject.fallback).to include("\##{commit.id}")
-      expect(subject.fallback).to include('succeeded')
-      expect(subject.attachments.first[:fields]).to be_empty
-    end
-  end
-
-  context 'when one of matrix builds failed' do
-    let(:color) { 'danger' }
-
-    it 'returns a message with information about failed build' do
-      commit.create_builds('master', false, nil)
-      first_build = commit.builds.first
-      second_build = commit.builds.last
-      first_build.update(status: "success")
-      second_build.update(status: "failed")
-
-      expect(subject.color).to eq(color)
-      expect(subject.fallback).to include('Commit')
-      expect(subject.fallback).to include("\##{commit.id}")
-      expect(subject.fallback).to include('failed')
-      expect(subject.attachments.first[:fields].size).to eq(1)
-      expect(subject.attachments.first[:fields].first[:title]).to eq(second_build.name)
-      expect(subject.attachments.first[:fields].first[:value]).to include("\##{second_build.id}")
-    end
-  end
-end
diff --git a/spec/models/ci/project_services/slack_service_spec.rb b/spec/models/ci/project_services/slack_service_spec.rb
deleted file mode 100644
index 1ac7dfe568da3332384fc312488d3bf747201306..0000000000000000000000000000000000000000
--- a/spec/models/ci/project_services/slack_service_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id         :integer          not null, primary key
-#  type       :string(255)
-#  title      :string(255)
-#  project_id :integer          not null
-#  created_at :datetime
-#  updated_at :datetime
-#  active     :boolean          default(FALSE), not null
-#  properties :text
-#
-
-require 'spec_helper'
-
-describe Ci::SlackService do
-  describe "Associations" do
-    it { is_expected.to belong_to :project }
-  end
-
-  describe "Validations" do
-    context "active" do
-      before do
-        subject.active = true
-      end
-
-      it { is_expected.to validate_presence_of :webhook }
-    end
-  end
-
-  describe "Execute" do
-    let(:slack)   { Ci::SlackService.new }
-    let(:commit)  { FactoryGirl.create :ci_commit }
-    let(:build)   { FactoryGirl.create :ci_build, commit: commit, status: 'failed' }
-    let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
-    let(:notify_only_broken_builds) { false }
-
-    before do
-      allow(slack).to receive_messages(
-        project: commit.project,
-        project_id: commit.project_id,
-        webhook: webhook_url,
-        notify_only_broken_builds: notify_only_broken_builds
-      )
-
-      WebMock.stub_request(:post, webhook_url)
-    end
-
-    it "should call Slack API" do
-      slack.execute(build)
-      Ci::SlackNotifierWorker.drain
-
-      expect(WebMock).to have_requested(:post, webhook_url).once
-    end
-  end
-end
diff --git a/spec/models/ci/project_spec.rb b/spec/models/ci/project_spec.rb
deleted file mode 100644
index ac7e38bbcb05dbfeea94325e345539e3baa0de20..0000000000000000000000000000000000000000
--- a/spec/models/ci/project_spec.rb
+++ /dev/null
@@ -1,246 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_projects
-#
-#  id                       :integer          not null, primary key
-#  name                     :string(255)
-#  timeout                  :integer          default(3600), not null
-#  created_at               :datetime
-#  updated_at               :datetime
-#  token                    :string(255)
-#  default_ref              :string(255)
-#  path                     :string(255)
-#  always_build             :boolean          default(FALSE), not null
-#  polling_interval         :integer
-#  public                   :boolean          default(FALSE), not null
-#  ssh_url_to_repo          :string(255)
-#  gitlab_id                :integer
-#  allow_git_fetch          :boolean          default(TRUE), not null
-#  email_recipients         :string(255)      default(""), not null
-#  email_add_pusher         :boolean          default(TRUE), not null
-#  email_only_broken_builds :boolean          default(TRUE), not null
-#  skip_refs                :string(255)
-#  coverage_regex           :string(255)
-#  shared_runners_enabled   :boolean          default(FALSE)
-#  generated_yaml_config    :text
-#
-
-require 'spec_helper'
-
-describe Ci::Project do
-  let(:project) { FactoryGirl.create :ci_project }
-  let(:gl_project) { project.gl_project }
-  subject { project }
-
-  it { is_expected.to have_many(:runner_projects) }
-  it { is_expected.to have_many(:runners) }
-  it { is_expected.to have_many(:web_hooks) }
-  it { is_expected.to have_many(:events) }
-  it { is_expected.to have_many(:variables) }
-  it { is_expected.to have_many(:triggers) }
-  it { is_expected.to have_many(:services) }
-
-  it { is_expected.to validate_presence_of :timeout }
-  it { is_expected.to validate_presence_of :gitlab_id }
-
-  describe 'before_validation' do
-    it 'should set an random token if none provided' do
-      project = FactoryGirl.create :ci_project_without_token
-      expect(project.token).not_to eq("")
-    end
-
-    it 'should not set an random toke if one provided' do
-      project = FactoryGirl.create :ci_project
-      expect(project.token).to eq("iPWx6WM4lhHNedGfBpPJNP")
-    end
-  end
-
-  describe :name_with_namespace do
-    subject { project.name_with_namespace }
-
-    it { is_expected.to eq(project.name) }
-    it { is_expected.to eq(gl_project.name_with_namespace) }
-  end
-
-  describe :path_with_namespace do
-    subject { project.path_with_namespace }
-
-    it { is_expected.to eq(project.path) }
-    it { is_expected.to eq(gl_project.path_with_namespace) }
-  end
-
-  describe :path_with_namespace do
-    subject { project.web_url }
-
-    it { is_expected.to eq(gl_project.web_url) }
-  end
-
-  describe :web_url do
-    subject { project.web_url }
-
-    it { is_expected.to eq(project.gitlab_url) }
-    it { is_expected.to eq(gl_project.web_url) }
-  end
-
-  describe :http_url_to_repo do
-    subject { project.http_url_to_repo }
-
-    it { is_expected.to eq(gl_project.http_url_to_repo) }
-  end
-
-  describe :ssh_url_to_repo do
-    subject { project.ssh_url_to_repo }
-
-    it { is_expected.to eq(gl_project.ssh_url_to_repo) }
-  end
-
-  describe :commits do
-    subject { project.commits }
-
-    before do
-      FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: gl_project
-    end
-
-    it { is_expected.to eq(gl_project.ci_commits) }
-  end
-
-  describe :builds do
-    subject { project.builds }
-
-    before do
-      commit = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: gl_project
-      FactoryGirl.create :ci_build, commit: commit
-    end
-
-    it { is_expected.to eq(gl_project.ci_builds) }
-  end
-
-  describe "ordered_by_last_commit_date" do
-    it "returns ordered projects" do
-      newest_project = FactoryGirl.create :empty_project
-      newest_ci_project = newest_project.ensure_gitlab_ci_project
-      oldest_project = FactoryGirl.create :empty_project
-      oldest_ci_project = oldest_project.ensure_gitlab_ci_project
-      project_without_commits = FactoryGirl.create :empty_project
-      ci_project_without_commits = project_without_commits.ensure_gitlab_ci_project
-
-      FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: newest_project
-      FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: oldest_project
-
-      expect(Ci::Project.ordered_by_last_commit_date).to eq([newest_ci_project, oldest_ci_project, ci_project_without_commits])
-    end
-  end
-
-  context :valid_project do
-    let(:commit) { FactoryGirl.create(:ci_commit) }
-
-    context :project_with_commit_and_builds do
-      let(:project) { commit.project }
-
-      before do
-        FactoryGirl.create(:ci_build, commit: commit)
-      end
-
-      it { expect(project.status).to eq('pending') }
-      it { expect(project.last_commit).to be_kind_of(Ci::Commit)  }
-      it { expect(project.human_status).to eq('pending') }
-    end
-  end
-
-  describe '#email_notification?' do
-    it do
-      project = FactoryGirl.create :ci_project, email_add_pusher: true
-      expect(project.email_notification?).to eq(true)
-    end
-
-    it do
-      project = FactoryGirl.create :ci_project, email_add_pusher: false, email_recipients: 'test tesft'
-      expect(project.email_notification?).to eq(true)
-    end
-
-    it do
-      project = FactoryGirl.create :ci_project, email_add_pusher: false, email_recipients: ''
-      expect(project.email_notification?).to eq(false)
-    end
-  end
-
-  describe '#broken_or_success?' do
-    it do
-      project = FactoryGirl.create :ci_project, email_add_pusher: true
-      allow(project).to receive(:broken?).and_return(true)
-      allow(project).to receive(:success?).and_return(true)
-      expect(project.broken_or_success?).to eq(true)
-    end
-
-    it do
-      project = FactoryGirl.create :ci_project, email_add_pusher: true
-      allow(project).to receive(:broken?).and_return(true)
-      allow(project).to receive(:success?).and_return(false)
-      expect(project.broken_or_success?).to eq(true)
-    end
-
-    it do
-      project = FactoryGirl.create :ci_project, email_add_pusher: true
-      allow(project).to receive(:broken?).and_return(false)
-      allow(project).to receive(:success?).and_return(true)
-      expect(project.broken_or_success?).to eq(true)
-    end
-
-    it do
-      project = FactoryGirl.create :ci_project, email_add_pusher: true
-      allow(project).to receive(:broken?).and_return(false)
-      allow(project).to receive(:success?).and_return(false)
-      expect(project.broken_or_success?).to eq(false)
-    end
-  end
-
-  describe :repo_url_with_auth do
-    let(:project) { FactoryGirl.create :ci_project }
-    subject { project.repo_url_with_auth }
-
-    it { is_expected.to be_a(String) }
-    it { is_expected.to end_with(".git") }
-    it { is_expected.to start_with(project.gitlab_url[0..6]) }
-    it { is_expected.to include(project.token) }
-    it { is_expected.to include('gitlab-ci-token') }
-    it { is_expected.to include(project.gitlab_url[7..-1]) }
-  end
-
-  describe :any_runners do
-    it "there are no runners available" do
-      project = FactoryGirl.create(:ci_project)
-      expect(project.any_runners?).to be_falsey
-    end
-
-    it "there is a specific runner" do
-      project = FactoryGirl.create(:ci_project)
-      project.runners << FactoryGirl.create(:ci_specific_runner)
-      expect(project.any_runners?).to be_truthy
-    end
-
-    it "there is a shared runner" do
-      project = FactoryGirl.create(:ci_project, shared_runners_enabled: true)
-      FactoryGirl.create(:ci_shared_runner)
-      expect(project.any_runners?).to be_truthy
-    end
-
-    it "there is a shared runner, but they are prohibited to use" do
-      project = FactoryGirl.create(:ci_project)
-      FactoryGirl.create(:ci_shared_runner)
-      expect(project.any_runners?).to be_falsey
-    end
-
-    it "checks the presence of specific runner" do
-      project = FactoryGirl.create(:ci_project)
-      specific_runner = FactoryGirl.create(:ci_specific_runner)
-      project.runners << specific_runner
-      expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
-    end
-
-    it "checks the presence of shared runner" do
-      project = FactoryGirl.create(:ci_project, shared_runners_enabled: true)
-      shared_runner = FactoryGirl.create(:ci_shared_runner)
-      expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
-    end
-  end
-end
diff --git a/spec/models/ci/runner_project_spec.rb b/spec/models/ci/runner_project_spec.rb
index 37682c6ea0cc81489e8f6f3e4d4b06b51043ee63..da8491357a5eb4357b897380a30072973fb413d2 100644
--- a/spec/models/ci/runner_project_spec.rb
+++ b/spec/models/ci/runner_project_spec.rb
@@ -11,6 +11,6 @@
 
 require 'spec_helper'
 
-describe Ci::RunnerProject do
+describe Ci::RunnerProject, models: true do
   pending "add some examples to (or delete) #{__FILE__}"
 end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 9a1233b909536c6daf629a10ea0f68d48f14c0c3..232760dfebac24d8da6017fbe9d894606182c1d0 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -19,7 +19,7 @@
 
 require 'spec_helper'
 
-describe Ci::Runner do
+describe Ci::Runner, models: true do
   describe '#display_name' do
     it 'should return the description if it has a value' do
       runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448')
@@ -38,7 +38,7 @@ describe Ci::Runner do
   end
 
   describe :assign_to do
-    let!(:project) { FactoryGirl.create :ci_project }
+    let!(:project) { FactoryGirl.create :empty_project }
     let!(:shared_runner) { FactoryGirl.create(:ci_shared_runner) }
 
     before { shared_runner.assign_to(project) }
@@ -116,8 +116,8 @@ describe Ci::Runner do
   describe "belongs_to_one_project?" do
     it "returns false if there are two projects runner assigned to" do
       runner = FactoryGirl.create(:ci_specific_runner)
-      project = FactoryGirl.create(:ci_project)
-      project1 = FactoryGirl.create(:ci_project)
+      project = FactoryGirl.create(:empty_project)
+      project1 = FactoryGirl.create(:empty_project)
       project.runners << runner
       project1.runners << runner
 
@@ -126,7 +126,7 @@ describe Ci::Runner do
 
     it "returns true" do
       runner = FactoryGirl.create(:ci_specific_runner)
-      project = FactoryGirl.create(:ci_project)
+      project = FactoryGirl.create(:empty_project)
       project.runners << runner
 
       expect(runner.belongs_to_one_project?).to be_truthy
diff --git a/spec/models/ci/service_spec.rb b/spec/models/ci/service_spec.rb
deleted file mode 100644
index 36cda988eb472fa79fc7312ecf77f2430a1e59f2..0000000000000000000000000000000000000000
--- a/spec/models/ci/service_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_services
-#
-#  id         :integer          not null, primary key
-#  type       :string(255)
-#  title      :string(255)
-#  project_id :integer          not null
-#  created_at :datetime
-#  updated_at :datetime
-#  active     :boolean          default(FALSE), not null
-#  properties :text
-#
-
-require 'spec_helper'
-
-describe Ci::Service do
-
-  describe "Associations" do
-    it { is_expected.to belong_to :project }
-  end
-
-  describe "Mass assignment" do
-  end
-
-  describe "Test Button" do
-    before do
-      @service = Ci::Service.new
-    end
-
-    describe "Testable" do
-      let(:commit) { FactoryGirl.create :ci_commit }
-      let(:build) { FactoryGirl.create :ci_build, commit: commit }
-
-      before do
-        allow(@service).to receive_messages(
-          project: commit.project
-        )
-        build
-        @testable = @service.can_test?
-      end
-
-      describe :can_test do
-        it { expect(@testable).to eq(true) }
-      end
-    end
-  end
-end
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index b8aa3c1e7775c468babbfb95cec25d59b85f3b82..cb2f51e2011f175be580450c4e78671ca00eac8b 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -12,8 +12,8 @@
 
 require 'spec_helper'
 
-describe Ci::Trigger do
-  let(:project) { FactoryGirl.create :ci_project }
+describe Ci::Trigger, models: true do
+  let(:project) { FactoryGirl.create :empty_project }
 
   describe 'before_validation' do
     it 'should set an random token if none provided' do
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index a515f5881ff00860f60842eb04555bc3da534318..31b56953a13af8645ca3a8807216837c950aae84 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -13,7 +13,7 @@
 
 require 'spec_helper'
 
-describe Ci::Variable do
+describe Ci::Variable, models: true do
   subject { Ci::Variable.new }
 
   let(:secret_value) { 'secret' }
diff --git a/spec/models/ci/web_hook_spec.rb b/spec/models/ci/web_hook_spec.rb
deleted file mode 100644
index 2865482a2120b35bc15654b48f3194e812654f57..0000000000000000000000000000000000000000
--- a/spec/models/ci/web_hook_spec.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# == Schema Information
-#
-# Table name: ci_web_hooks
-#
-#  id         :integer          not null, primary key
-#  url        :string(255)      not null
-#  project_id :integer          not null
-#  created_at :datetime
-#  updated_at :datetime
-#
-
-require 'spec_helper'
-
-describe Ci::WebHook do
-  describe "Associations" do
-    it { is_expected.to belong_to :project }
-  end
-
-  describe "Validations" do
-    it { is_expected.to validate_presence_of(:url) }
-
-    context "url format" do
-      it { is_expected.to allow_value("http://example.com").for(:url) }
-      it { is_expected.to allow_value("https://excample.com").for(:url) }
-      it { is_expected.to allow_value("http://test.com/api").for(:url) }
-      it { is_expected.to allow_value("http://test.com/api?key=abc").for(:url) }
-      it { is_expected.to allow_value("http://test.com/api?key=abc&type=def").for(:url) }
-
-      it { is_expected.not_to allow_value("example.com").for(:url) }
-      it { is_expected.not_to allow_value("ftp://example.com").for(:url) }
-      it { is_expected.not_to allow_value("herp-and-derp").for(:url) }
-    end
-  end
-
-  describe "execute" do
-    before(:each) do
-      @web_hook = FactoryGirl.create(:ci_web_hook)
-      @project = @web_hook.project
-      @data = { before: 'oldrev', after: 'newrev', ref: 'ref' }
-
-      WebMock.stub_request(:post, @web_hook.url)
-    end
-
-    it "POSTs to the web hook URL" do
-      @web_hook.execute(@data)
-      expect(WebMock).to have_requested(:post, @web_hook.url).once
-    end
-
-    it "POSTs the data as JSON" do
-      json = @data.to_json
-
-      @web_hook.execute(@data)
-      expect(WebMock).to have_requested(:post, @web_hook.url).with(body: json).once
-    end
-
-    it "catches exceptions" do
-      expect(Ci::WebHook).to receive(:post).and_raise("Some HTTP Post error")
-
-      expect{ @web_hook.execute(@data) }.
-        to raise_error(RuntimeError, 'Some HTTP Post error')
-    end
-  end
-end
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 1031af097bd0c396d043c3186f459b3f82dc16ac..9307d97e2141c28c741616a79c4999a27659a80e 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -1,56 +1,78 @@
 require 'spec_helper'
 
-describe CommitRange do
+describe CommitRange, models: true do
   describe 'modules' do
     subject { described_class }
 
     it { is_expected.to include_module(Referable) }
   end
 
-  let(:sha_from) { 'f3f85602' }
-  let(:sha_to)   { 'e86e1013' }
+  let!(:project) { create(:project, :public) }
+  let!(:commit1) { project.commit("HEAD~2") }
+  let!(:commit2) { project.commit }
 
-  let(:range)  { described_class.new("#{sha_from}...#{sha_to}") }
-  let(:range2) { described_class.new("#{sha_from}..#{sha_to}") }
+  let(:sha_from) { commit1.short_id }
+  let(:sha_to)   { commit2.short_id }
+
+  let(:full_sha_from) { commit1.id }
+  let(:full_sha_to)   { commit2.id }
+
+  let(:range)  { described_class.new("#{sha_from}...#{sha_to}", project) }
+  let(:range2) { described_class.new("#{sha_from}..#{sha_to}", project) }
 
   it 'raises ArgumentError when given an invalid range string' do
-    expect { described_class.new("Foo") }.to raise_error(ArgumentError)
+    expect { described_class.new("Foo", project) }.to raise_error(ArgumentError)
   end
 
   describe '#to_s' do
     it 'is correct for three-dot syntax' do
-      expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}"
+      expect(range.to_s).to eq "#{full_sha_from}...#{full_sha_to}"
     end
 
     it 'is correct for two-dot syntax' do
-      expect(range2.to_s).to eq "#{sha_from[0..7]}..#{sha_to[0..7]}"
+      expect(range2.to_s).to eq "#{full_sha_from}..#{full_sha_to}"
     end
   end
 
   describe '#to_reference' do
-    let(:project) { double('project', to_reference: 'namespace1/project') }
+    let(:cross) { create(:project) }
+
+    it 'returns a String reference to the object' do
+      expect(range.to_reference).to eq "#{full_sha_from}...#{full_sha_to}"
+    end
+
+    it 'returns a String reference to the object' do
+      expect(range2.to_reference).to eq "#{full_sha_from}..#{full_sha_to}"
+    end
+
+    it 'supports a cross-project reference' do
+      expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{full_sha_from}...#{full_sha_to}"
+    end
+  end
 
-    before do
-      range.project = project
+  describe '#reference_link_text' do
+    let(:cross) { create(:project) }
+
+    it 'returns a String reference to the object' do
+      expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}"
     end
 
     it 'returns a String reference to the object' do
-      expect(range.to_reference).to eq range.to_s
+      expect(range2.reference_link_text).to eq "#{sha_from}..#{sha_to}"
     end
 
     it 'supports a cross-project reference' do
-      cross = double('project')
-      expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{range.to_s}"
+      expect(range.reference_link_text(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}"
     end
   end
 
   describe '#reference_title' do
     it 'returns the correct String for three-dot ranges' do
-      expect(range.reference_title).to eq "Commits #{sha_from} through #{sha_to}"
+      expect(range.reference_title).to eq "Commits #{full_sha_from} through #{full_sha_to}"
     end
 
     it 'returns the correct String for two-dot ranges' do
-      expect(range2.reference_title).to eq "Commits #{sha_from}^ through #{sha_to}"
+      expect(range2.reference_title).to eq "Commits #{full_sha_from}^ through #{full_sha_to}"
     end
   end
 
@@ -60,11 +82,11 @@ describe CommitRange do
     end
 
     it 'includes the correct values for a three-dot range' do
-      expect(range.to_param).to eq({ from: sha_from, to: sha_to })
+      expect(range.to_param).to eq({ from: full_sha_from, to: full_sha_to })
     end
 
     it 'includes the correct values for a two-dot range' do
-      expect(range2.to_param).to eq({ from: sha_from + '^', to: sha_to })
+      expect(range2.to_param).to eq({ from: full_sha_from + '^', to: full_sha_to })
     end
   end
 
@@ -79,64 +101,37 @@ describe CommitRange do
   end
 
   describe '#valid_commits?' do
-    context 'without a project' do
-      it 'returns nil' do
-        expect(range.valid_commits?).to be_nil
+    context 'with a valid repo' do
+      before do
+        expect(project).to receive(:valid_repo?).and_return(true)
       end
-    end
-
-    it 'accepts an optional project argument' do
-      project1 = double('project1').as_null_object
-      project2 = double('project2').as_null_object
-
-      # project1 gets assigned through the accessor, but ignored when not given
-      # as an argument to `valid_commits?`
-      expect(project1).not_to receive(:present?)
-      range.project = project1
-
-      # project2 gets passed to `valid_commits?`
-      expect(project2).to receive(:present?).and_return(false)
 
-      range.valid_commits?(project2)
-    end
-
-    context 'with a project' do
-      let(:project) { double('project', repository: double('repository')) }
+      it 'is false when `sha_from` is invalid' do
+        expect(project).to receive(:commit).with(sha_from).and_return(nil)
+        expect(project).to receive(:commit).with(sha_to).and_call_original
 
-      context 'with a valid repo' do
-        before do
-          expect(project).to receive(:valid_repo?).and_return(true)
-          range.project = project
-        end
+        expect(range).not_to be_valid_commits
+      end
 
-        it 'is false when `sha_from` is invalid' do
-          expect(project.repository).to receive(:commit).with(sha_from).and_return(false)
-          expect(project.repository).not_to receive(:commit).with(sha_to)
-          expect(range).not_to be_valid_commits
-        end
+      it 'is false when `sha_to` is invalid' do
+        expect(project).to receive(:commit).with(sha_from).and_call_original
+        expect(project).to receive(:commit).with(sha_to).and_return(nil)
 
-        it 'is false when `sha_to` is invalid' do
-          expect(project.repository).to receive(:commit).with(sha_from).and_return(true)
-          expect(project.repository).to receive(:commit).with(sha_to).and_return(false)
-          expect(range).not_to be_valid_commits
-        end
+        expect(range).not_to be_valid_commits
+      end
 
-        it 'is true when both `sha_from` and `sha_to` are valid' do
-          expect(project.repository).to receive(:commit).with(sha_from).and_return(true)
-          expect(project.repository).to receive(:commit).with(sha_to).and_return(true)
-          expect(range).to be_valid_commits
-        end
+      it 'is true when both `sha_from` and `sha_to` are valid' do
+        expect(range).to be_valid_commits
       end
+    end
 
-      context 'without a valid repo' do
-        before do
-          expect(project).to receive(:valid_repo?).and_return(false)
-          range.project = project
-        end
+    context 'without a valid repo' do
+      before do
+        expect(project).to receive(:valid_repo?).and_return(false)
+      end
 
-        it 'returns false' do
-          expect(range).not_to be_valid_commits
-        end
+      it 'returns false' do
+        expect(range).not_to be_valid_commits
       end
     end
   end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 90be93249511f5b8eef45d4b5d65587758c37553..ecf37b40c587fa288e985f581d6d4947dbab718a 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Commit do
+describe Commit, models: true do
   let(:project) { create(:project) }
   let(:commit)  { project.commit }
 
@@ -24,6 +24,17 @@ describe Commit do
     end
   end
 
+  describe '#reference_link_text' do
+    it 'returns a String reference to the object' do
+      expect(commit.reference_link_text).to eq commit.short_id
+    end
+
+    it 'supports a cross-project reference' do
+      cross = double('project')
+      expect(commit.reference_link_text(cross)).to eq "#{project.to_reference}@#{commit.short_id}"
+    end
+  end
+
   describe '#title' do
     it "returns no_commit_message when safe_message is blank" do
       allow(commit).to receive(:safe_message).and_return('')
@@ -77,14 +88,10 @@ eos
     let(:other_issue) { create :issue, project: other_project }
 
     it 'detects issues that this commit is marked as closing' do
-      allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid}")
-      expect(commit.closes_issues).to eq([issue])
-    end
-
-    it 'does not detect issues from other projects' do
       ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
-      allow(commit).to receive(:safe_message).and_return("Fixes #{ext_ref}")
-      expect(commit.closes_issues).to be_empty
+      allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}")
+      expect(commit.closes_issues).to include(issue)
+      expect(commit.closes_issues).to include(other_issue)
     end
   end
 
@@ -100,4 +107,15 @@ eos
     # Include the subject in the repository stub.
     let(:extra_commits) { [subject] }
   end
+
+  describe '#hook_attrs' do
+    let(:data) { commit.hook_attrs(with_changed_files: true) }
+
+    it { expect(data).to be_a(Hash) }
+    it { expect(data[:message]).to include('Add submodule from gitlab.com') }
+    it { expect(data[:timestamp]).to eq('2014-02-27T11:01:38+02:00') }
+    it { expect(data[:added]).to eq(["gitlab-grack"]) }
+    it { expect(data[:modified]).to eq([".gitmodules"]) }
+    it { expect(data[:removed]).to eq([]) }
+  end
 end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index dca0715eed8f5a72419ed6711ad96bef406e55de..b8f901b34335151b77cac7f8ca3ffc1d940c231c 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -33,18 +33,19 @@
 
 require 'spec_helper'
 
-describe CommitStatus do
+describe CommitStatus, models: true do
   let(:commit) { FactoryGirl.create :ci_commit }
   let(:commit_status) { FactoryGirl.create :commit_status, commit: commit }
 
   it { is_expected.to belong_to(:commit) }
   it { is_expected.to belong_to(:user) }
+  it { is_expected.to belong_to(:project) }
+
   it { is_expected.to validate_presence_of(:name) }
   it { is_expected.to validate_inclusion_of(:status).in_array(%w(pending running failed success canceled)) }
 
   it { is_expected.to delegate_method(:sha).to(:commit) }
   it { is_expected.to delegate_method(:short_sha).to(:commit) }
-  it { is_expected.to delegate_method(:gl_project).to(:commit) }
   
   it { is_expected.to respond_to :success? }
   it { is_expected.to respond_to :failed? }
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index f7ed30f81984a27a5e2b6a1f21bebee1f9e8feb0..25b3f4e50dafe5057370f6b0df2312076e1a4d71 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe CaseSensitivity do
+describe CaseSensitivity, models: true do
   describe '.iwhere' do
     let(:connection) { ActiveRecord::Base.connection }
     let(:model)      { Class.new { include CaseSensitivity } }
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 0f13c4410cdc482943c7d9e17c25b804e81acb57..021d62cdf0ce324711e69333ece64ed84f2484c8 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -81,4 +81,36 @@ describe Issue, "Issuable" do
       expect(hook_data[:object_attributes]).to eq(issue.hook_attrs)
     end
   end
+
+  describe '#card_attributes' do
+    it 'includes the author name' do
+      allow(issue).to receive(:author).and_return(double(name: 'Robert'))
+      allow(issue).to receive(:assignee).and_return(nil)
+
+      expect(issue.card_attributes).
+        to eq({ 'Author' => 'Robert', 'Assignee' => nil })
+    end
+
+    it 'includes the assignee name' do
+      allow(issue).to receive(:author).and_return(double(name: 'Robert'))
+      allow(issue).to receive(:assignee).and_return(double(name: 'Douwe'))
+
+      expect(issue.card_attributes).
+        to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' })
+    end
+  end
+
+  describe "votes" do
+    before do
+      author = create :user
+      project = create :empty_project
+      issue.notes.awards.create!(note: "thumbsup", author: author, project: project)
+      issue.notes.awards.create!(note: "thumbsdown", author: author, project: project)
+    end
+
+    it "returns correct values" do
+      expect(issue.upvotes).to eq(1)
+      expect(issue.downvotes).to eq(1)
+    end
+  end
 end
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 6179882e93591f4556ddc9062b5c3ebba2dd14c0..20f0c561e44b16e3f9970f387ecc0a8d57e14fd8 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -1,5 +1,22 @@
 require 'spec_helper'
 
+describe Mentionable do
+  include Mentionable
+
+  def author
+    nil
+  end
+
+  describe :references do
+    let(:project) { create(:project) }
+
+    it 'excludes JIRA references' do
+      allow(project).to receive_messages(jira_tracker?: true)
+      expect(referenced_mentionables(project, 'JIRA-123')).to be_empty
+    end
+  end
+end
+
 describe Issue, "Mentionable" do
   describe '#mentioned_users' do
     let!(:user) { create(:user, username: 'stranger') }
diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6445e29c3efb3584ad71f1617c9fdfa4940e9587
--- /dev/null
+++ b/spec/models/concerns/strip_attribute_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Milestone, "StripAttribute" do
+  let(:milestone) { create(:milestone) }
+
+  describe ".strip_attributes" do
+    it { expect(Milestone).to respond_to(:strip_attributes) }
+    it { expect(Milestone.strip_attrs).to include(:title) }
+  end
+
+  describe "#strip_attributes" do
+    before do
+      milestone.title = '    8.3   '
+      milestone.valid?
+    end
+
+    it { expect(milestone.title).to eq('8.3') }
+  end
+
+end
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..30c0a04b84045b315094c1fb9a9b380de70f4a53
--- /dev/null
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+
+shared_examples 'TokenAuthenticatable' do
+  describe 'dynamically defined methods' do
+    it { expect(described_class).to be_private_method_defined(:generate_token) }
+    it { expect(described_class).to be_private_method_defined(:write_new_token) }
+    it { expect(described_class).to respond_to("find_by_#{token_field}") }
+    it { is_expected.to respond_to("ensure_#{token_field}") }
+    it { is_expected.to respond_to("reset_#{token_field}!") }
+  end
+end
+
+describe User, 'TokenAuthenticatable' do
+  let(:token_field) { :authentication_token }
+  it_behaves_like 'TokenAuthenticatable'
+
+  describe 'ensures authentication token' do
+    subject { create(:user).send(token_field) }
+    it { is_expected.to be_a String }
+  end
+end
+
+describe ApplicationSetting, 'TokenAuthenticatable' do
+  let(:token_field) { :runners_registration_token }
+  it_behaves_like 'TokenAuthenticatable'
+
+  describe 'generating new token' do
+    context 'token is not generated yet' do
+      describe 'token field accessor' do
+        subject { described_class.new.send(token_field) }
+        it { is_expected.to_not be_blank }
+      end
+
+      describe 'ensured token' do
+        subject { described_class.new.send("ensure_#{token_field}") }
+
+        it { is_expected.to be_a String }
+        it { is_expected.to_not be_blank }
+      end
+
+      describe 'ensured! token' do
+        subject { described_class.new.send("ensure_#{token_field}!") }
+
+        it 'should persist new token' do
+          expect(subject).to eq described_class.current[token_field]
+        end
+      end
+    end
+
+    context 'token is generated' do
+      before { subject.send("reset_#{token_field}!") }
+      it 'persists a new token 'do
+        expect(subject.send(:read_attribute, token_field)).to be_a String
+      end
+    end
+  end
+
+  describe 'multiple token fields' do
+    before do
+      described_class.send(:add_authentication_token_field, :yet_another_token)
+    end
+
+    describe '.token_fields' do
+      subject { described_class.authentication_token_fields }
+      it { is_expected.to include(:runners_registration_token, :yet_another_token) }
+    end
+  end
+end
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index 957299324595f1fbcfeb5b6308958dc64475a60b..64ba778afea6f3957ed538a1e069c0fd9e569974 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -15,7 +15,7 @@
 
 require 'spec_helper'
 
-describe DeployKey do
+describe DeployKey, models: true do
   let(:project) { create(:project) }
   let(:deploy_key) { create(:deploy_key, projects: [project]) }
 
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 0eb22599d18d9a487868d38ed65fa8f905c617d5..8aedbfb86360e925904b2fd5eb110d2da718b358 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -11,7 +11,7 @@
 
 require 'spec_helper'
 
-describe DeployKeysProject do
+describe DeployKeysProject, models: true do
   describe "Associations" do
     it { is_expected.to belong_to(:deploy_key) }
     it { is_expected.to belong_to(:project) }
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index ae53f7a536b25638795a87bf8cda1ebe48acb19f..071582b028219395f2e9da775c591b805c3f289c 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -16,7 +16,7 @@
 
 require 'spec_helper'
 
-describe Event do
+describe Event, models: true do
   describe "Associations" do
     it { is_expected.to belong_to(:project) }
     it { is_expected.to belong_to(:target) }
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index 7744610db786c23858cc61361629a0dc0caacad2..6ec6b9037a4c295661e00e22766bfce53759d577 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe ExternalIssue do
+describe ExternalIssue, models: true do
   let(:project) { double('project', to_reference: 'namespace1/project1') }
   let(:issue)   { described_class.new('EXT-1234', project) }
 
diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/external_wiki_service_spec.rb
index 4bd5b0be61cc231decb94aada0961278b7e3b0a0..b198aa775268e502b4be30c25bc156279882b21b 100644
--- a/spec/models/external_wiki_service_spec.rb
+++ b/spec/models/external_wiki_service_spec.rb
@@ -20,7 +20,7 @@
 
 require 'spec_helper'
 
-describe ExternalWikiService do
+describe ExternalWikiService, models: true do
   include ExternalWikiHelper
   describe "Associations" do
     it { should belong_to :project }
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index c86314c454c82f965f272b83f8aafc4cbb9e3e3d..d61c1c96bde6af83531a90be55624411da8db360 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -33,7 +33,7 @@
 
 require 'spec_helper'
 
-describe GenericCommitStatus do
+describe GenericCommitStatus, models: true do
   let(:commit) { FactoryGirl.create :ci_commit }
   let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, commit: commit }
 
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index 6eeff30b20e1ef88195ffed18991855a43ccddb5..197c99cd007ea9c80f30ef2474210b85d1d57907 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe GlobalMilestone do
+describe GlobalMilestone, models: true do
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
   let(:group) { create(:group) }
@@ -62,4 +62,14 @@ describe GlobalMilestone do
       expect(@global_milestone.milestones.count).to eq(3)
     end
   end
+
+  describe :safe_title do
+    let(:milestone) { create(:milestone, title: "git / test", project: project1) }
+
+    it 'should strip out slashes and spaces' do
+      global_milestone = GlobalMilestone.new(milestone.title, [milestone])
+
+      expect(global_milestone.safe_title).to eq('git-test')
+    end
+  end
 end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 6f166b5ab7527c60f3442e513085603a8d6a74e0..646f767e6fe3f6a5e47b37720ec69f0d522e5c49 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -16,7 +16,7 @@
 
 require 'spec_helper'
 
-describe Group do
+describe Group, models: true do
   let!(:group) { create(:group) }
 
   describe 'associations' do
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index a2dc66fce3ea4df78eb27a8469b6a66323cbc140..645ee0b929a7ff6d0a09fd2f3af9aff175f3adea 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -18,7 +18,7 @@
 
 require 'spec_helper'
 
-describe ProjectHook do
+describe ProjectHook, models: true do
   describe '.push_hooks' do
     it 'should return hooks for push events only' do
       hook = create(:project_hook, push_events: true)
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 16641c12124d865df1a8a2596af8b0542f5e5046..1455661485bf5a993a0a9f5c89d2e67fa9ea8545 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -18,7 +18,7 @@
 
 require "spec_helper"
 
-describe ServiceHook do
+describe ServiceHook, models: true do
   describe "Associations" do
     it { is_expected.to belong_to :service }
   end
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 02d2cc2c77a72eb6bc8bf3cf83d34efbd65659f3..138b87a9a061c642f6ffa2adef4e136bbd04a874 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -18,7 +18,7 @@
 
 require "spec_helper"
 
-describe SystemHook do
+describe SystemHook, models: true do
   describe "execute" do
     before(:each) do
       @system_hook = create(:system_hook)
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 2fdc49f02ee26bee5043a558e835507f68244dfe..2d90b0793cc74226326f28488759269334c39f94 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -18,7 +18,7 @@
 
 require 'spec_helper'
 
-describe ProjectHook do
+describe ProjectHook, models: true do
   describe "Associations" do
     it { is_expected.to belong_to :project }
   end
@@ -71,5 +71,11 @@ describe ProjectHook do
 
       expect { @project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError)
     end
+
+    it "handles SSL exceptions" do
+      expect(WebHook).to receive(:post).and_raise(OpenSSL::SSL::SSLError.new('SSL error'))
+
+      expect(@project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
+    end
   end
 end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index c9aa1b063c660cd04f8bd9ab6b0984c1df4052fb..52271c7c8c6d1fde914f43e5417f4bf94fcbbfa2 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -20,7 +20,7 @@
 
 require 'spec_helper'
 
-describe Issue do
+describe Issue, models: true do
   describe "Associations" do
     it { is_expected.to belong_to(:milestone) }
   end
diff --git a/spec/models/jira_issue_spec.rb b/spec/models/jira_issue_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1634265b439d1c18e8fd5ae4f010aa91cdc83cf1
--- /dev/null
+++ b/spec/models/jira_issue_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe JiraIssue do
+  let(:project) { create(:project) }
+  subject { JiraIssue.new('JIRA-123', project) }
+
+  describe 'id' do
+    subject { super().id }
+    it { is_expected.to eq('JIRA-123') }
+  end
+
+  describe 'iid' do
+    subject { super().iid }
+    it { is_expected.to eq('JIRA-123') }
+  end
+
+  describe 'to_s' do
+    subject { super().to_s }
+    it { is_expected.to eq('JIRA-123') }
+  end
+
+  describe :== do
+    specify { expect(subject).to eq(JiraIssue.new('JIRA-123', project)) }
+    specify { expect(subject).not_to eq(JiraIssue.new('JIRA-124', project)) }
+
+    it 'only compares with JiraIssues' do
+      expect(subject).not_to eq('JIRA-123')
+    end
+  end
+end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 2f819f60cbb62cf40205d8c9655db8e44c8b4cd8..c962b83644a045664a6df0c86de8a401d0fb9588 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -15,7 +15,7 @@
 
 require 'spec_helper'
 
-describe Key do
+describe Key, models: true do
   describe "Associations" do
     it { is_expected.to belong_to(:user) }
   end
@@ -81,7 +81,7 @@ describe Key do
 
     it 'rejects the multiple line key' do
       key = build(:key)
-      key.key.gsub!(' ', "\n")
+      key.key.tr!(' ', "\n")
       expect(key).not_to be_valid
     end
   end
diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb
index 8c240826582807ad8614fc75602e4d9e7f2a0f9b..dc7510b1de305aeb100608a3b682b2f2a07bebda 100644
--- a/spec/models/label_link_spec.rb
+++ b/spec/models/label_link_spec.rb
@@ -12,7 +12,7 @@
 
 require 'spec_helper'
 
-describe LabelLink do
+describe LabelLink, models: true do
   let(:label) { create(:label_link) }
   it { expect(label).to be_valid }
 
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 511ee8cbd96521de19c941f32786003706e59964..696fbf7e0aa0a87e3d3f6abd6f4c6c4646b67435 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -13,7 +13,7 @@
 
 require 'spec_helper'
 
-describe Label do
+describe Label, models: true do
   let(:label) { create(:label) }
 
   describe 'associations' do
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 57f840c1e9164d726ee1fc449c9a38d3747c1bfa..2aedca20df257c96ce6b57a523a447b6f53fd5c1 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -19,7 +19,7 @@
 
 require 'spec_helper'
 
-describe Member do
+describe Member, models: true do
   describe "Associations" do
     it { is_expected.to belong_to(:user) }
   end
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index 652026729bb056c6f00ce87c60a533a9e1a17148..5424c9b9cba9fb4dc96b6aa40dee2597b45ce4b7 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -19,7 +19,7 @@
 
 require 'spec_helper'
 
-describe GroupMember do
+describe GroupMember, models: true do
   context 'notification' do
     describe "#after_create" do
       it "should send email to user" do
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index ee912bf12a2017e72cf154043ad2c7ec5cff99d1..9f26d9eb5ce592805dcae168b7cabd57847ec31b 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -19,7 +19,7 @@
 
 require 'spec_helper'
 
-describe ProjectMember do
+describe ProjectMember, models: true do
   describe :import_team do
     before do
       @abilities = Six.new
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index edf211c85c1b3df2d0da1873a4bc649dd09e3059..0d3901f4d0014c92620eb8c890083ca9619b8011 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -25,13 +25,13 @@
 
 require 'spec_helper'
 
-describe MergeRequest do
+describe MergeRequest, models: true do
   subject { create(:merge_request) }
 
   describe 'associations' do
     it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') }
     it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') }
-
+    it { is_expected.to belong_to(:merge_user).class_name("User") }
     it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) }
   end
 
@@ -48,12 +48,32 @@ describe MergeRequest do
   describe 'validation' do
     it { is_expected.to validate_presence_of(:target_branch) }
     it { is_expected.to validate_presence_of(:source_branch) }
+
+    context "Validation of merge user with Merge When Build succeeds" do
+      it "allows user to be nil when the feature is disabled" do
+        expect(subject).to be_valid
+      end
+
+      it "is invalid without merge user" do
+        subject.merge_when_build_succeeds = true
+        expect(subject).not_to be_valid
+      end
+
+      it "is valid with merge user" do
+        subject.merge_when_build_succeeds = true
+        subject.merge_user = build(:user)
+
+        expect(subject).to be_valid
+      end
+    end
   end
 
   describe 'respond to' do
     it { is_expected.to respond_to(:unchecked?) }
     it { is_expected.to respond_to(:can_be_merged?) }
     it { is_expected.to respond_to(:cannot_be_merged?) }
+    it { is_expected.to respond_to(:merge_params) }
+    it { is_expected.to respond_to(:merge_when_build_succeeds) }
   end
 
   describe '#to_reference' do
@@ -144,6 +164,17 @@ describe MergeRequest do
 
       expect(subject.closes_issues).to include(issue2)
     end
+
+    context 'for a project with JIRA integration' do
+      let(:issue0) { JiraIssue.new('JIRA-123', subject.project) }
+      let(:issue1) { JiraIssue.new('FOOBAR-4567', subject.project) }
+
+      it 'returns sorted JiraIssues' do
+        allow(subject.project).to receive_messages(default_branch: subject.target_branch)
+
+        expect(subject.closes_issues).to eq([issue0, issue1])
+      end
+    end
   end
 
   describe "#work_in_progress?" do
@@ -187,6 +218,50 @@ describe MergeRequest do
     end
   end
 
+  describe '#can_remove_source_branch?' do
+    let(:user) { create(:user) }
+    let(:user2) { create(:user) }
+
+    before do
+      subject.source_project.team << [user, :master]
+
+      subject.source_branch = "feature"
+      subject.target_branch = "master"
+      subject.save!
+    end
+
+    it "can't be removed when its a protected branch" do
+      allow(subject.source_project).to receive(:protected_branch?).and_return(true)
+      expect(subject.can_remove_source_branch?(user)).to be_falsey
+    end
+
+    it "cant remove a root ref" do
+      subject.source_branch = "master"
+      subject.target_branch = "feature"
+
+      expect(subject.can_remove_source_branch?(user)).to be_falsey
+    end
+
+    it "is unable to remove the source branch for a project the user cannot push to" do
+      expect(subject.can_remove_source_branch?(user2)).to be_falsey
+    end
+
+    it "is can be removed in all other cases" do
+      expect(subject.can_remove_source_branch?(user)).to be_truthy
+    end
+  end
+
+  describe "#reset_merge_when_build_succeeds" do
+    let(:merge_if_green) { create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user) }
+
+    it "sets the item to false" do
+      merge_if_green.reset_merge_when_build_succeeds
+      merge_if_green.reload
+
+      expect(merge_if_green.merge_when_build_succeeds).to be_falsey
+    end
+  end
+
   describe "#hook_attrs" do
     it "has all the required keys" do
       attrs = subject.hook_attrs
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 77c586273220685649cfe4f42a7604cf732b39b9..30a71987d86d6fa572769ce88a40b153a53c31cc 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -15,7 +15,7 @@
 
 require 'spec_helper'
 
-describe Milestone do
+describe Milestone, models: true do
   describe "Associations" do
     it { is_expected.to belong_to(:project) }
     it { is_expected.to have_many(:issues) }
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index a98b9cb7321205b8791f4fbf47ed298ee4635ffe..4fa2d2bc4d26b790af8888ef8619b260697a14fc 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -16,7 +16,7 @@
 
 require 'spec_helper'
 
-describe Namespace do
+describe Namespace, models: true do
   let!(:namespace) { create(:namespace) }
 
   it { is_expected.to have_many :projects }
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index f347f537550814028dccc8f35e141a3cfb5495f4..593d8f76215de366247727af77df18a42926ff5e 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -16,11 +16,12 @@
 #  system        :boolean          default(FALSE), not null
 #  st_diff       :text
 #  updated_by_id :integer
+#  is_award      :boolean          default(FALSE), not null
 #
 
 require 'spec_helper'
 
-describe Note do
+describe Note, models: true do
   describe 'associations' do
     it { is_expected.to belong_to(:project) }
     it { is_expected.to belong_to(:noteable) }
@@ -136,9 +137,40 @@ describe Note do
       create :note, note: "smile", is_award: true
     end
 
-    it "returns grouped array of notes" do
-      expect(Note.grouped_awards.first.first).to eq("smile")
-      expect(Note.grouped_awards.first.last).to match_array(Note.all)
+    it "returns grouped hash of notes" do
+      expect(Note.grouped_awards.keys.size).to eq(3)
+      expect(Note.grouped_awards["smile"]).to match_array(Note.all)
+    end
+
+    it "returns thumbsup and thumbsdown always" do
+      expect(Note.grouped_awards["thumbsup"]).to match_array(Note.none)
+      expect(Note.grouped_awards["thumbsdown"]).to match_array(Note.none)
+    end
+  end
+
+  describe "editable?" do
+    it "returns true" do
+      note = build(:note)
+      expect(note.editable?).to be_truthy
+    end
+
+    it "returns false" do
+      note = build(:note, system: true)
+      expect(note.editable?).to be_falsy
+    end
+
+    it "returns false" do
+      note = build(:note, is_award: true, note: "smiley")
+      expect(note.editable?).to be_falsy
+    end
+  end
+  
+  describe "set_award!" do
+    let(:issue) { create :issue }
+
+    it "converts aliases to actual name" do
+      note = create :note, note: ":+1:", noteable: issue
+      expect(note.reload.note).to eq("thumbsup")
     end
   end
 end
diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb
index f600a240c46b5fa45c4646595607fb5efe0cbfce..3643ad1b0523e7b5d4d63b77a0922ec4551ae9a5 100644
--- a/spec/models/project_security_spec.rb
+++ b/spec/models/project_security_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Project do
+describe Project, models: true do
   describe :authorization do
     before do
       @p1 = create(:project)
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 230807ea672b02b24bc037e3f63b217e4cf5e8cd..88cd624877a8e18f1e7de718ab9705bd59fd8e5f 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -20,7 +20,7 @@
 
 require 'spec_helper'
 
-describe BuildkiteService do
+describe BuildkiteService, models: true do
   describe 'Associations' do
     it { is_expected.to belong_to :project }
     it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index e9967f5fe0b078fde7e412230ceca47bda195178..a2cf68a9e387c037dda62ce3ab5b10850f5d55d3 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -20,7 +20,7 @@
 
 require 'spec_helper'
 
-describe DroneCiService do
+describe DroneCiService, models: true do
   describe 'associations' do
     it { is_expected.to belong_to(:project) }
     it { is_expected.to have_one(:service_hook) }
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index 16296607a945da99c87268037e442949fe2eaafb..ff7fbcaa00443d634bbe83344254e0eb31f924a0 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -20,7 +20,7 @@
 
 require 'spec_helper'
 
-describe FlowdockService do
+describe FlowdockService, models: true do
   describe "Associations" do
     it { is_expected.to belong_to :project }
     it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
index 9e1564723168da1f1d049593589388ba65a7a3f7..ecb3ccb16732b4a5c4d7ae8f70983edf1c96f09c 100644
--- a/spec/models/project_services/gemnasium_service_spec.rb
+++ b/spec/models/project_services/gemnasium_service_spec.rb
@@ -20,7 +20,7 @@
 
 require 'spec_helper'
 
-describe GemnasiumService do
+describe GemnasiumService, models: true do
   describe "Associations" do
     it { is_expected.to belong_to :project }
     it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb
deleted file mode 100644
index b9006b693b218075fd595035b151c325788a4d61..0000000000000000000000000000000000000000
--- a/spec/models/project_services/gitlab_ci_service_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# == Schema Information
-#
-# Table name: services
-#
-#  id                    :integer          not null, primary key
-#  type                  :string(255)
-#  title                 :string(255)
-#  project_id            :integer
-#  created_at            :datetime
-#  updated_at            :datetime
-#  active                :boolean          default(FALSE), not null
-#  properties            :text
-#  template              :boolean          default(FALSE)
-#  push_events           :boolean          default(TRUE)
-#  issues_events         :boolean          default(TRUE)
-#  merge_requests_events :boolean          default(TRUE)
-#  tag_push_events       :boolean          default(TRUE)
-#  note_events           :boolean          default(TRUE), not null
-#
-
-require 'spec_helper'
-
-describe GitlabCiService do
-  describe 'associations' do
-    it { is_expected.to belong_to(:project) }
-    it { is_expected.to have_one(:service_hook) }
-  end
-
-  describe 'commits methods' do
-    before do
-      @ci_project = create(:ci_project)
-      @service = GitlabCiService.new
-      allow(@service).to receive_messages(
-        service_hook: true,
-        project_url: 'http://ci.gitlab.org/projects/2',
-        token: 'verySecret',
-        project: @ci_project.gl_project
-      )
-    end
-
-    describe :build_page do
-      it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://localhost/#{@ci_project.gl_project.path_with_namespace}/commit/2ab7834c/builds")}
-    end
-
-    describe "execute" do
-      let(:user)    { create(:user, username: 'username') }
-      let(:project) { create(:project, name: 'project') }
-      let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
-
-      it "calls CreateCommitService" do
-        expect_any_instance_of(Ci::CreateCommitService).to receive(:execute).with(@ci_project, user, push_sample_data)
-
-        @service.execute(push_sample_data)
-      end
-    end
-  end
-end
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index e34ca09bffc94f9afac55b7cde8e21e594140ad2..3518dbd172863d435f4a34489a7fbfb63c225b74 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -20,7 +20,7 @@
 
 require 'spec_helper'
 
-describe GitlabIssueTrackerService do
+describe GitlabIssueTrackerService, models: true do
   describe "Associations" do
     it { is_expected.to belong_to :project }
     it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index f67d7b309808a395b6a1616ddbc98eb15221843c..91dd92b7c679aa06af80e1c27c365a27ac950039 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -20,7 +20,7 @@
 
 require 'spec_helper'
 
-describe HipchatService do
+describe HipchatService, models: true do
   describe "Associations" do
     it { is_expected.to belong_to :project }
     it { is_expected.to have_one :service_hook }
@@ -57,23 +57,21 @@ describe HipchatService do
 
     it 'should use v1 if version is provided' do
       allow(hipchat).to receive(:api_version).and_return('v1')
-      expect(HipChat::Client).to receive(:new).
-                                     with(token,
-                                          api_version: 'v1',
-                                          server_url: server_url).
-                                     and_return(
-                                         double(:hipchat_service).as_null_object)
+      expect(HipChat::Client).to receive(:new).with(
+        token,
+        api_version: 'v1',
+        server_url: server_url
+      ).and_return(double(:hipchat_service).as_null_object)
       hipchat.execute(push_sample_data)
     end
 
     it 'should use v2 as the version when nothing is provided' do
       allow(hipchat).to receive(:api_version).and_return('')
-      expect(HipChat::Client).to receive(:new).
-                                     with(token,
-                                          api_version: 'v2',
-                                          server_url: server_url).
-                                     and_return(
-                                         double(:hipchat_service).as_null_object)
+      expect(HipChat::Client).to receive(:new).with(
+        token,
+        api_version: 'v2',
+        server_url: server_url
+      ).and_return(double(:hipchat_service).as_null_object)
       hipchat.execute(push_sample_data)
     end
 
@@ -247,6 +245,55 @@ describe HipchatService do
       end
     end
 
+    context 'build events' do
+      let(:build) { create(:ci_build) }
+      let(:data) { Gitlab::BuildDataBuilder.build(build) }
+
+      context 'for failed' do
+        before { build.drop }
+
+        it "should call Hipchat API" do
+          hipchat.execute(data)
+
+          expect(WebMock).to have_requested(:post, api_url).once
+        end
+
+        it "should create a build message" do
+          message = hipchat.send(:create_build_message, data)
+
+          project_url = project.web_url
+          project_name = project.name_with_namespace.gsub(/\s/, '')
+          sha = data[:sha]
+          ref = data[:ref]
+          ref_type = data[:tag] ? 'tag' : 'branch'
+          duration = data[:commit][:duration]
+
+          expect(message).to eq("<a href=\"#{project_url}\">#{project_name}</a>: " \
+            "Commit <a href=\"#{project_url}/commit/#{sha}/builds\">#{Commit.truncate_sha(sha)}</a> " \
+            "of <a href=\"#{project_url}/commits/#{ref}\">#{ref}</a> #{ref_type} " \
+            "by #{data[:commit][:author_name]} failed in #{duration} second(s)")
+        end
+      end
+
+      context 'for succeeded' do
+        before do
+          build.success
+        end
+
+        it "should call Hipchat API" do
+          hipchat.notify_only_broken_builds = false
+          hipchat.execute(data)
+          expect(WebMock).to have_requested(:post, api_url).once
+        end
+
+        it "should notify only broken" do
+          hipchat.notify_only_broken_builds = true
+          hipchat.execute(data)
+          expect(WebMock).to_not have_requested(:post, api_url).once
+        end
+      end
+    end
+
     context "#message_options" do
       it "should be set to the defaults" do
         expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'yellow' })
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index 7d483a44c53e4bfc292a4c8a47cec1aaa471dba1..b783b1a576ec8dc931f4aa356c11b338d755ab22 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -22,7 +22,7 @@ require 'spec_helper'
 require 'socket'
 require 'json'
 
-describe IrkerService do
+describe IrkerService, models: true do
   describe 'Associations' do
     it { is_expected.to belong_to :project }
     it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index ddd2cce212c5750bd3e35143a40c179cddb8ab34..2f8193170aedf3009a9377879a2b4da0c7c11c08 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -20,12 +20,119 @@
 
 require 'spec_helper'
 
-describe JiraService do
+describe JiraService, models: true do
   describe "Associations" do
     it { is_expected.to belong_to :project }
     it { is_expected.to have_one :service_hook }
   end
 
+  describe "Execute" do
+    let(:user)    { create(:user) }
+    let(:project) { create(:project) }
+    let(:merge_request) { create(:merge_request) }
+
+    before do
+      @jira_service = JiraService.new
+      allow(@jira_service).to receive_messages(
+        project_id: project.id,
+        project: project,
+        service_hook: true,
+        project_url: 'http://jira.example.com',
+        username: 'gitlab_jira_username',
+        password: 'gitlab_jira_password'
+      )
+      @jira_service.save # will build API URL, as api_url was not specified above
+      @sample_data = Gitlab::PushDataBuilder.build_sample(project, user)
+      # https://github.com/bblimke/webmock#request-with-basic-authentication
+      @api_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/transitions'
+      @comment_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/comment'
+
+      WebMock.stub_request(:post, @api_url)
+      WebMock.stub_request(:post, @comment_url)
+    end
+
+    it "should call JIRA API" do
+      @jira_service.execute(merge_request, JiraIssue.new("JIRA-123", project))
+      expect(WebMock).to have_requested(:post, @comment_url).with(
+        body: /Issue solved with/
+      ).once
+    end
+
+    it "calls the api with jira_issue_transition_id" do
+      @jira_service.jira_issue_transition_id = 'this-is-a-custom-id'
+      @jira_service.execute(merge_request, JiraIssue.new("JIRA-123", project))
+      expect(WebMock).to have_requested(:post, @api_url).with(
+        body: /this-is-a-custom-id/
+      ).once
+    end
+  end
+
+  describe "Stored password invalidation" do
+    let(:project) { create(:project) }
+
+    context "when a password was previously set" do
+      before do
+        @jira_service = JiraService.create(
+          project: create(:project),
+          properties: {
+            api_url: 'http://jira.example.com/rest/api/2',
+            username: 'mic',
+            password: "password"
+          }
+        )
+      end
+
+      it "reset password if url changed" do
+        @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
+        @jira_service.save
+        expect(@jira_service.password).to be_nil
+      end
+
+      it "does not reset password if username changed" do
+        @jira_service.username = "some_name"
+        @jira_service.save
+        expect(@jira_service.password).to eq("password")
+      end
+
+      it "does not reset password if new url is set together with password, even if it's the same password" do
+        @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
+        @jira_service.password = 'password'
+        @jira_service.save
+        expect(@jira_service.password).to eq("password")
+        expect(@jira_service.api_url).to eq("http://jira_edited.example.com/rest/api/2")
+      end
+
+      it "should reset password if url changed, even if setter called multiple times" do
+        @jira_service.api_url = 'http://jira1.example.com/rest/api/2'
+        @jira_service.api_url = 'http://jira1.example.com/rest/api/2'
+        @jira_service.save
+        expect(@jira_service.password).to be_nil
+      end
+    end
+
+    context "when no password was previously set" do
+      before do
+        @jira_service = JiraService.create(
+          project: create(:project),
+          properties: {
+            api_url: 'http://jira.example.com/rest/api/2',
+            username: 'mic'
+          }
+        )
+      end
+
+      it "saves password if new url is set together with password" do
+        @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
+        @jira_service.password = 'password'
+        @jira_service.save
+        expect(@jira_service.password).to eq("password")
+        expect(@jira_service.api_url).to eq("http://jira_edited.example.com/rest/api/2")
+      end
+
+    end
+  end
+
+
   describe "Validations" do
     context "active" do
       before do
@@ -78,11 +185,12 @@ describe JiraService do
 
     context 'when gitlab.yml was initialized' do
       before do
-        settings = { "jira" => {
-          "title" => "Jira",
-          "project_url" => "http://jira.sample/projects/project_a",
-          "issues_url" => "http://jira.sample/issues/:id",
-          "new_issue_url" => "http://jira.sample/projects/project_a/issues/new"
+        settings = {
+          "jira" => {
+            "title" => "Jira",
+            "project_url" => "http://jira.sample/projects/project_a",
+            "issues_url" => "http://jira.sample/issues/:id",
+            "new_issue_url" => "http://jira.sample/projects/project_a/issues/new"
           }
         }
         allow(Gitlab.config).to receive(:issues_tracker).and_return(settings)
@@ -94,9 +202,9 @@ describe JiraService do
       end
 
       it 'should be prepopulated with the settings' do
-        expect(@service.properties[:project_url]).to eq('http://jira.sample/projects/project_a')
-        expect(@service.properties[:issues_url]).to eq("http://jira.sample/issues/:id")
-        expect(@service.properties[:new_issue_url]).to eq("http://jira.sample/projects/project_a/issues/new")
+        expect(@service.properties["project_url"]).to eq('http://jira.sample/projects/project_a')
+        expect(@service.properties["issues_url"]).to eq("http://jira.sample/issues/:id")
+        expect(@service.properties["new_issue_url"]).to eq("http://jira.sample/projects/project_a/issues/new")
       end
     end
   end
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index ac10ffbd39b0115cf550e398f1054cfdcb592eb2..96039f9491bc49619de645fe3ccc8835296b0283 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -20,7 +20,7 @@
 
 require 'spec_helper'
 
-describe PushoverService do
+describe PushoverService, models: true do
   describe 'Associations' do
     it { is_expected.to belong_to :project }
     it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/slack_service/build_message_spec.rb b/spec/models/project_services/slack_service/build_message_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..621c83c0cda355a42d7485c52a814270aaf62bb6
--- /dev/null
+++ b/spec/models/project_services/slack_service/build_message_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe SlackService::BuildMessage do
+  subject { SlackService::BuildMessage.new(args) }
+
+  let(:args) do
+    {
+      sha: '97de212e80737a608d939f648d959671fb0a0142',
+      ref: 'develop',
+      tag: false,
+
+      project_name: 'project_name',
+      project_url: 'somewhere.com',
+
+      commit: {
+        status: status,
+        author_name: 'hacker',
+        duration: 10,
+      },
+    }
+  end
+
+  context 'succeeded' do
+    let(:status) { 'success' }
+    let(:color) { 'good' }
+
+    it 'returns a message with information about succeeded build' do
+      message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker passed in 10 second(s)'
+      expect(subject.pretext).to be_empty
+      expect(subject.fallback).to eq(message)
+      expect(subject.attachments).to eq([text: message, color: color])
+    end
+  end
+
+  context 'failed' do
+    let(:status) { 'failed' }
+    let(:color) { 'danger' }
+
+    it 'returns a message with information about failed build' do
+      message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 10 second(s)'
+      expect(subject.pretext).to be_empty
+      expect(subject.fallback).to eq(message)
+      expect(subject.attachments).to eq([text: message, color: color])
+    end
+  end
+end
diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/slack_service/issue_message_spec.rb
index b78d92f23a15fff8f42e15d57c4550ac07ffc633..97e6f03e308a70e0b7846c227d859549a9873b21 100644
--- a/spec/models/project_services/slack_service/issue_message_spec.rb
+++ b/spec/models/project_services/slack_service/issue_message_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe SlackService::IssueMessage do
+describe SlackService::IssueMessage, models: true do
   subject { SlackService::IssueMessage.new(args) }
 
   let(:args) do
diff --git a/spec/models/project_services/slack_service/merge_message_spec.rb b/spec/models/project_services/slack_service/merge_message_spec.rb
index 581c50d6c883f6e245fde1f7084d76bbc46425be..dae8bd90922a4416b476b7b6689790cb4ebb23d3 100644
--- a/spec/models/project_services/slack_service/merge_message_spec.rb
+++ b/spec/models/project_services/slack_service/merge_message_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe SlackService::MergeMessage do
+describe SlackService::MergeMessage, models: true do
   subject { SlackService::MergeMessage.new(args) }
 
   let(:args) do
diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb
index 21fb575480b8fd091bf0a55ac97b4cc91895acfd..06006b9a4f530d525f28217f30be90550b64a4e1 100644
--- a/spec/models/project_services/slack_service/note_message_spec.rb
+++ b/spec/models/project_services/slack_service/note_message_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe SlackService::NoteMessage do
+describe SlackService::NoteMessage, models: true do
   let(:color) { '#345' }
 
   before do
@@ -89,10 +89,10 @@ describe SlackService::NoteMessage do
     it 'returns a message regarding notes on an issue' do
       message = SlackService::NoteMessage.new(@args)
       expect(message.pretext).to eq(
-                                     "Test User commented on " \
-      "<url|issue #20> in <somewhere.com|project_name>: " \
-      "*issue title*")
-      expected_attachments =  [
+        "Test User commented on " \
+        "<url|issue #20> in <somewhere.com|project_name>: " \
+        "*issue title*")
+      expected_attachments = [
           {
               text: "comment on an issue",
               color: color,
diff --git a/spec/models/project_services/slack_service/push_message_spec.rb b/spec/models/project_services/slack_service/push_message_spec.rb
index ddc290820d10c57425186e43494d1997f0d1b2e1..cda9ee670b0dcd13f0b2538129960a9915090e5e 100644
--- a/spec/models/project_services/slack_service/push_message_spec.rb
+++ b/spec/models/project_services/slack_service/push_message_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe SlackService::PushMessage do
+describe SlackService::PushMessage, models: true do
   subject { SlackService::PushMessage.new(args) }
 
   let(:args) do
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 97b60e19e407971a02a182166d24588781254964..a9e0afad90fc190f28abcb6de5645710c16f4517 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -20,7 +20,7 @@
 
 require 'spec_helper'
 
-describe SlackService do
+describe SlackService, models: true do
   describe "Associations" do
     it { is_expected.to belong_to :project }
     it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index 3e8f106d27fd90e5c32fc839cc80db60ce6bf907..cc92eb0bd9f2cd098eefb8710b4ae9ef32e56244 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -17,7 +17,7 @@
 
 require 'spec_helper'
 
-describe ProjectSnippet do
+describe ProjectSnippet, models: true do
   describe "Associations" do
     it { is_expected.to belong_to(:project) }
   end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index f80fada45e9446b5d17bb3918f19833c2db8ff56..400bdf2d962bcdf35ded86db2715f44aba542fa4 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -28,11 +28,12 @@
 #  import_type            :string(255)
 #  import_source          :string(255)
 #  commit_count           :integer          default(0)
+#  import_error           :text
 #
 
 require 'spec_helper'
 
-describe Project do
+describe Project, models: true do
   describe 'associations' do
     it { is_expected.to belong_to(:group) }
     it { is_expected.to belong_to(:namespace) }
@@ -53,6 +54,13 @@ describe Project do
     it { is_expected.to have_one(:slack_service).dependent(:destroy) }
     it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
     it { is_expected.to have_one(:asana_service).dependent(:destroy) }
+    it { is_expected.to have_many(:commit_statuses) }
+    it { is_expected.to have_many(:ci_commits) }
+    it { is_expected.to have_many(:builds) }
+    it { is_expected.to have_many(:runner_projects) }
+    it { is_expected.to have_many(:runners) }
+    it { is_expected.to have_many(:variables) }
+    it { is_expected.to have_many(:triggers) }
   end
 
   describe 'modules' do
@@ -87,6 +95,18 @@ describe Project do
       expect(project2.errors[:limit_reached].first).to match(/Your project limit is 0/)
     end
   end
+  
+  describe 'project token' do
+    it 'should set an random token if none provided' do
+      project = FactoryGirl.create :empty_project, runners_token: ''
+      expect(project.runners_token).not_to eq('')
+    end
+
+    it 'should not set an random toke if one provided' do
+      project = FactoryGirl.create :empty_project, runners_token: 'my-token'
+      expect(project.runners_token).to eq('my-token')
+    end
+  end
 
   describe 'Respond to' do
     it { is_expected.to respond_to(:url_to_repo) }
@@ -152,13 +172,17 @@ describe Project do
 
   describe '#get_issue' do
     let(:project) { create(:empty_project) }
-    let(:issue)   { create(:issue, project: project) }
+    let!(:issue)  { create(:issue, project: project) }
 
     context 'with default issues tracker' do
       it 'returns an issue' do
         expect(project.get_issue(issue.iid)).to eq issue
       end
 
+      it 'returns count of open issues' do
+        expect(project.open_issues_count).to eq(1)
+      end
+
       it 'returns nil when no issue found' do
         expect(project.get_issue(999)).to be_nil
       end
@@ -394,12 +418,7 @@ describe Project do
 
   describe :ci_commit do
     let(:project) { create :project }
-    let(:commit) { create :ci_commit, gl_project: project }
-
-    before do
-      project.ensure_gitlab_ci_project
-      project.create_gitlab_ci_service(active: true)
-    end
+    let(:commit) { create :ci_commit, project: project }
 
     it { expect(project.ci_commit(commit.sha)).to eq(commit) }
   end
@@ -411,9 +430,7 @@ describe Project do
 
     subject { project.builds_enabled }
 
-    it { is_expected.to eq(project.gitlab_ci_service.active) }
     it { expect(project.builds_enabled?).to be_truthy }
-    it { expect(project.gitlab_ci_project).to be_a(Ci::Project) }
   end
 
   describe '.trending' do
@@ -444,7 +461,9 @@ describe Project do
 
       before do
         2.times do
-          create(:note_on_commit, project: project2, created_at: date)
+          # Little fix for special issue related to Fractional Seconds support for MySQL.
+          # See: https://github.com/rails/rails/pull/14359/files
+          create(:note_on_commit, project: project2, created_at: date + 1)
         end
       end
 
@@ -472,4 +491,89 @@ describe Project do
       it { is_expected.to eq([]) }
     end
   end
+
+  context 'shared runners by default' do
+    let(:project) { create(:empty_project) }
+
+    subject { project.shared_runners_enabled }
+
+    context 'are enabled' do
+      before { stub_application_setting(shared_runners_enabled: true) }
+
+      it { is_expected.to be_truthy }
+    end
+
+    context 'are disabled' do
+      before { stub_application_setting(shared_runners_enabled: false) }
+
+      it { is_expected.to be_falsey }
+    end
+  end
+
+  describe :any_runners do
+    let(:project) { create(:empty_project, shared_runners_enabled: shared_runners_enabled) }
+    let(:specific_runner) { create(:ci_specific_runner) }
+    let(:shared_runner) { create(:ci_shared_runner) }
+
+    context 'for shared runners disabled' do
+      let(:shared_runners_enabled) { false }
+      
+      it 'there are no runners available' do
+        expect(project.any_runners?).to be_falsey
+      end
+  
+      it 'there is a specific runner' do
+        project.runners << specific_runner
+        expect(project.any_runners?).to be_truthy
+      end
+  
+      it 'there is a shared runner, but they are prohibited to use' do
+        shared_runner
+        expect(project.any_runners?).to be_falsey
+      end
+  
+      it 'checks the presence of specific runner' do
+        project.runners << specific_runner
+        expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
+      end
+    end
+    
+    context 'for shared runners enabled' do
+      let(:shared_runners_enabled) { true }
+      
+      it 'there is a shared runner' do
+        shared_runner
+        expect(project.any_runners?).to be_truthy
+      end
+
+      it 'checks the presence of shared runner' do
+        shared_runner
+        expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
+      end
+    end
+  end
+
+  describe '#visibility_level_allowed?' do
+    let(:project) { create :project, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+
+    context 'when checking on non-forked project' do
+      it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
+      it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
+      it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_truthy }
+    end
+
+    context 'when checking on forked project' do
+      let(:forked_project) { create :forked_project_with_submodules }
+
+      before do
+        forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, forked_from_project_id: project.id)
+        forked_project.save
+      end
+
+      it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
+      it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
+      it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
+    end
+
+  end
 end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 26e8fdae472cdcee832d28ffe0f8fdee50840955..5cd5ae327bf2da88eda2d83de61e58828e2f39fe 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe ProjectTeam do
+describe ProjectTeam, models: true do
   let(:master) { create(:user) }
   let(:reporter) { create(:user) }
   let(:guest) { create(:user) }
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 3b8891444476f7a48c7b06337eacaa426da29b41..876b927eaeaca8b9f9b60208bf71cca64ab7549e 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe ProjectWiki do
+describe ProjectWiki, models: true do
   let(:project) { create(:empty_project) }
   let(:repository) { project.repository }
   let(:user) { project.owner }
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index 1e6937b536c418581c8800c7ab348c6116cc46c1..7e956cf67797621d2326b68c20d3980df50de176 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -12,7 +12,7 @@
 
 require 'spec_helper'
 
-describe ProtectedBranch do
+describe ProtectedBranch, models: true do
   describe 'Associations' do
     it { is_expected.to belong_to(:project) }
   end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 319fa0a7c8d70cae60ade1943744b1b8c6581ea9..afbf62035acef575c2b44e9d59e2dc4e5ce3da6d 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1,9 +1,10 @@
 require 'spec_helper'
 
-describe Repository do
+describe Repository, models: true do
   include RepoHelpers
 
   let(:repository) { create(:project).repository }
+  let(:user) { create(:user) }
 
   describe :branch_names_contains do
     subject { repository.branch_names_contains(sample_commit.id) }
@@ -99,5 +100,123 @@ describe Repository do
       it { expect(subject.startline).to eq(186) }
       it { expect(subject.data.lines[2]).to eq("  - Feature: Replace teams with group membership\n") }
     end
+
+  end
+
+  describe "#license" do
+    before do
+      repository.send(:cache).expire(:license)
+      TestBlob = Struct.new(:name)
+    end
+
+    it 'test selection preference' do
+      files = [TestBlob.new('file'), TestBlob.new('license'), TestBlob.new('copying')]
+      expect(repository.tree).to receive(:blobs).and_return(files)
+
+      expect(repository.license.name).to eq('license')
+    end
+
+    it 'also accepts licence instead of license' do
+      expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('licence')])
+
+      expect(repository.license.name).to eq('licence')
+    end
+  end
+
+  describe :add_branch do
+    context 'when pre hooks were successful' do
+      it 'should run without errors' do
+        hook = double(trigger: true)
+        expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
+
+        expect { repository.add_branch(user, 'new_feature', 'master') }.not_to raise_error
+      end
+
+      it 'should create the branch' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true)
+
+        branch = repository.add_branch(user, 'new_feature', 'master')
+
+        expect(branch.name).to eq('new_feature')
+      end
+    end
+
+    context 'when pre hooks failed' do
+      it 'should get an error' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false)
+
+        expect do
+          repository.add_branch(user, 'new_feature', 'master')
+        end.to raise_error(GitHooksService::PreReceiveError)
+      end
+
+      it 'should not create the branch' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false)
+
+        expect do
+          repository.add_branch(user, 'new_feature', 'master')
+        end.to raise_error(GitHooksService::PreReceiveError)
+        expect(repository.find_branch('new_feature')).to be_nil
+      end
+    end
+  end
+
+  describe :rm_branch do
+    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)
+
+        expect { repository.rm_branch(user, 'feature') }.not_to raise_error
+      end
+
+      it 'should delete the branch' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true)
+
+        expect { repository.rm_branch(user, 'feature') }.not_to raise_error
+
+        expect(repository.find_branch('feature')).to be_nil
+      end
+    end
+
+    context 'when pre hooks failed' do
+      it 'should get an error' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false)
+
+        expect do
+          repository.rm_branch(user, 'new_feature')
+        end.to raise_error(GitHooksService::PreReceiveError)
+      end
+
+      it 'should not delete the branch' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false)
+
+        expect do
+          repository.rm_branch(user, 'feature')
+        end.to raise_error(GitHooksService::PreReceiveError)
+        expect(repository.find_branch('feature')).not_to be_nil
+      end
+    end
+  end
+
+  describe :commit_with_hooks do
+    context 'when pre hooks were successful' do
+      it 'should run without errors' do
+        expect_any_instance_of(GitHooksService).to receive(:execute).and_return(true)
+
+        expect do
+          repository.commit_with_hooks(user, 'feature') { sample_commit.id }
+        end.not_to raise_error
+      end
+    end
+
+    context 'when pre hooks failed' do
+      it 'should get an error' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false)
+
+        expect do
+          repository.commit_with_hooks(user, 'feature') { sample_commit.id }
+        end.to raise_error(GitHooksService::PreReceiveError)
+      end
+    end
   end
 end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 692e5fda3ba9e7ead1d78e1b57bd42a6281f1b38..0ca82365b98f56ed25df9a5420cfcd7fe63c7f34 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -20,7 +20,7 @@
 
 require 'spec_helper'
 
-describe Service do
+describe Service, models: true do
 
   describe "Associations" do
     it { is_expected.to belong_to :project }
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 8158183867597a418741324f8868115311a83413..eb2dbbdc5a4eafba13290efd26e219556ca55d03 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -17,7 +17,7 @@
 
 require 'spec_helper'
 
-describe Snippet do
+describe Snippet, models: true do
   describe 'modules' do
     subject { described_class }
 
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 4631b12faf1763f79547e1c9daf5e34e32466d0d..2f184bbaf92c50fffd79529ea173d84a89d50421 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -26,6 +26,7 @@
 #  bio                        :string(255)
 #  failed_attempts            :integer          default(0)
 #  locked_at                  :datetime
+#  unlock_token               :string(255)
 #  username                   :string(255)
 #  can_create_group           :boolean          default(TRUE), not null
 #  can_create_team            :boolean          default(TRUE), not null
@@ -56,11 +57,12 @@
 #  project_view               :integer          default(0)
 #  consumed_timestep          :integer
 #  layout                     :integer          default(0)
+#  hide_project_limit         :boolean          default(FALSE)
 #
 
 require 'spec_helper'
 
-describe User do
+describe User, models: true do
   include Gitlab::CurrentSettings
 
   describe 'modules' do
@@ -91,7 +93,23 @@ describe User do
   end
 
   describe 'validations' do
-    it { is_expected.to validate_presence_of(:username) }
+    describe 'username' do
+      it 'validates presence' do
+        expect(subject).to validate_presence_of(:username)
+      end
+
+      it 'rejects blacklisted names' do
+        user = build(:user, username: 'dashboard')
+
+        expect(user).not_to be_valid
+        expect(user.errors.values).to eq [['dashboard is a reserved name']]
+      end
+
+      it 'validates uniqueness' do
+        expect(subject).to validate_uniqueness_of(:username)
+      end
+    end
+
     it { is_expected.to validate_presence_of(:projects_limit) }
     it { is_expected.to validate_numericality_of(:projects_limit) }
     it { is_expected.to allow_value(0).for(:projects_limit) }
@@ -445,8 +463,8 @@ describe User do
       expect(User.search(user1.username.downcase).to_a).to eq([user1])
       expect(User.search(user2.username.upcase).to_a).to eq([user2])
       expect(User.search(user2.username.downcase).to_a).to eq([user2])
-      expect(User.search(user1.username.downcase).to_a.count).to eq(2)
-      expect(User.search(user2.username.downcase).to_a.count).to eq(1)
+      expect(User.search(user1.username.downcase).to_a.size).to eq(2)
+      expect(User.search(user2.username.downcase).to_a.size).to eq(1)
     end
   end
 
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index d7802d1734f7274cde8c83a41d7b651b9e9a8450..c1b03838aa9da41ac1cc12c6c011dfc160b40582 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -1,6 +1,6 @@
 require "spec_helper"
 
-describe WikiPage do
+describe WikiPage, models: true do
   let(:project) { create(:empty_project) }
   let(:user) { project.owner }
   let(:wiki) { ProjectWiki.new(project, user) }
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 5c1b58535cc3b636122791e49e27a0e309b3950a..36461e84c3ab7c33283415c82505036c35d1e0d1 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -118,7 +118,7 @@ describe API::API, api: true  do
            branch_name: 'new design',
            ref: branch_sha
       expect(response.status).to eq(400)
-      expect(json_response['message']).to eq('Branch name invalid')
+      expect(json_response['message']).to eq('Branch name is invalid')
     end
 
     it 'should return 400 if branch already exists' do
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 13cced81875c59f7680a92082e72c9c89542a56d..4cfa49d15666d06deeee90d6ba0181acde3978c5 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -10,6 +10,8 @@ describe API::API, api: true  do
   let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
   let!(:group1) { create(:group, avatar: File.open(avatar_file_path)) }
   let!(:group2) { create(:group) }
+  let!(:project1) { create(:project, namespace: group1) }
+  let!(:project2) { create(:project, namespace: group2) }
 
   before do
     group1.add_owner(user1)
@@ -67,7 +69,7 @@ describe API::API, api: true  do
       it "should return any existing group" do
         get api("/groups/#{group2.id}", admin)
         expect(response.status).to eq(200)
-        json_response['name'] == group2.name
+        expect(json_response['name']).to eq(group2.name)
       end
 
       it "should not return a non existing group" do
@@ -80,7 +82,7 @@ describe API::API, api: true  do
       it 'should return any existing group' do
         get api("/groups/#{group1.path}", admin)
         expect(response.status).to eq(200)
-        json_response['name'] == group2.name
+        expect(json_response['name']).to eq(group1.name)
       end
 
       it 'should not return a non existing group' do
@@ -95,6 +97,59 @@ describe API::API, api: true  do
     end
   end
 
+  describe "GET /groups/:id/projects" do
+    context "when authenticated as user" do
+      it "should return the group's projects" do
+        get api("/groups/#{group1.id}/projects", user1)
+        expect(response.status).to eq(200)
+        expect(json_response.length).to eq(1)
+        expect(json_response.first['name']).to eq(project1.name)
+      end
+
+      it "should not return a non existing group" do
+        get api("/groups/1328/projects", user1)
+        expect(response.status).to eq(404)
+      end
+
+      it "should not return a group not attached to user1" do
+        get api("/groups/#{group2.id}/projects", user1)
+        expect(response.status).to eq(403)
+      end
+    end
+
+    context "when authenticated as admin" do
+      it "should return any existing group" do
+        get api("/groups/#{group2.id}/projects", admin)
+        expect(response.status).to eq(200)
+        expect(json_response.length).to eq(1)
+        expect(json_response.first['name']).to eq(project2.name)
+      end
+
+      it "should not return a non existing group" do
+        get api("/groups/1328/projects", admin)
+        expect(response.status).to eq(404)
+      end
+    end
+
+    context 'when using group path in URL' do
+      it 'should return any existing group' do
+        get api("/groups/#{group1.path}/projects", admin)
+        expect(response.status).to eq(200)
+        expect(json_response.first['name']).to eq(project1.name)
+      end
+
+      it 'should not return a non existing group' do
+        get api('/groups/unknown/projects', admin)
+        expect(response.status).to eq(404)
+      end
+
+      it 'should not return a group not attached to user1' do
+        get api("/groups/#{group2.path}/projects", user1)
+        expect(response.status).to eq(403)
+      end
+    end
+  end
+
   describe "POST /groups" do
     context "when authenticated as user without group permissions" do
       it "should not create group" do
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index aff109a942469fe8d37b61ec2ed8a5a2ed0034b9..667f0dbea5cdf8ca34144af044ca56fddae9dcfc 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -47,7 +47,7 @@ describe API::API, api: true  do
            name: 'Foo',
            color: '#FFAA'
       expect(response.status).to eq(400)
-      expect(json_response['message']['color']).to eq(['is invalid'])
+      expect(json_response['message']['color']).to eq(['must be a valid color code'])
     end
 
     it 'should return 400 for too long color code' do
@@ -55,7 +55,7 @@ describe API::API, api: true  do
            name: 'Foo',
            color: '#FFAAFFFF'
       expect(response.status).to eq(400)
-      expect(json_response['message']['color']).to eq(['is invalid'])
+      expect(json_response['message']['color']).to eq(['must be a valid color code'])
     end
 
     it 'should return 400 for invalid name' do
@@ -151,12 +151,12 @@ describe API::API, api: true  do
       expect(json_response['message']['title']).to eq(['is invalid'])
     end
 
-    it 'should return 400 for invalid name' do
+    it 'should return 400 when color code is too short' do
       put api("/projects/#{project.id}/labels", user),
           name: 'label1',
           color: '#FF'
       expect(response.status).to eq(400)
-      expect(json_response['message']['color']).to eq(['is invalid'])
+      expect(json_response['message']['color']).to eq(['must be a valid color code'])
     end
 
     it 'should return 400 for too long color code' do
@@ -164,7 +164,7 @@ describe API::API, api: true  do
            name: 'Foo',
            color: '#FFAAFFFF'
       expect(response.status).to eq(400)
-      expect(json_response['message']['color']).to eq(['is invalid'])
+      expect(json_response['message']['color']).to eq(['must be a valid color code'])
     end
   end
 end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index a68c7b1e461d3fbee2f5d3bcf7c95d15a56b430e..e194eb93cf48757cd7b21ee53080be87ab74a8c3 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -6,7 +6,7 @@ describe API::API, api: true  do
   let(:user) { create(:user) }
   let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
   let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
-  let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.seconds) }
+  let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
   let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) }
   let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
   let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
@@ -131,6 +131,23 @@ describe API::API, api: true  do
     end
   end
 
+  describe 'GET /projects/:id/merge_request/:merge_request_id/commits' do
+    context 'valid merge request' do
+      before { get api("/projects/#{project.id}/merge_request/#{merge_request.id}/commits", user) }
+      let(:commit) { merge_request.commits.first }
+
+      it { expect(response.status).to eq 200 }
+      it { expect(json_response.size).to eq(merge_request.commits.size) }
+      it { expect(json_response.first['id']).to eq(commit.id) }
+      it { expect(json_response.first['title']).to eq(commit.title) }
+    end
+
+    it 'returns a 404 when merge_request_id not found' do
+      get api("/projects/#{project.id}/merge_request/999/commits", user)
+      expect(response.status).to eq(404)
+    end
+  end
+
   describe 'GET /projects/:id/merge_request/:merge_request_id/changes' do
     it 'should return the change information of the merge_request' do
       get api("/projects/#{project.id}/merge_request/#{merge_request.id}/changes", user)
@@ -303,19 +320,21 @@ describe API::API, api: true  do
   end
 
   describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do
+    let(:ci_commit) { create(:ci_commit_without_jobs) }
+
     it "should return merge_request in case of success" do
       put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
 
       expect(response.status).to eq(200)
     end
 
-    it "should return 405 if branch can't be merged" do
+    it "should return 406 if branch can't be merged" do
       allow_any_instance_of(MergeRequest).
         to receive(:can_be_merged?).and_return(false)
 
       put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
 
-      expect(response.status).to eq(405)
+      expect(response.status).to eq(406)
       expect(json_response['message']).to eq('Branch cannot be merged')
     end
 
@@ -340,6 +359,17 @@ describe API::API, api: true  do
       expect(response.status).to eq(401)
       expect(json_response['message']).to eq('401 Unauthorized')
     end
+
+    it "enables merge when build succeeds if the ci is active" do
+      allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
+      allow(ci_commit).to receive(:active?).and_return(true)
+
+      put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user), merge_when_build_succeeds: true
+
+      expect(response.status).to eq(200)
+      expect(json_response['title']).to eq('Test')
+      expect(json_response['merge_when_build_succeeds']).to eq(true)
+    end
   end
 
   describe "PUT /projects/:id/merge_request/:merge_request_id" do
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 606b226ad770f0f5de622b5fc8dedc1119d3745f..142b637d2913a2512801d58cb3b1f7335dc37dee 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -1,11 +1,17 @@
 require 'spec_helper'
 
-describe API::API, 'ProjectHooks', api: true  do
+describe API::API, 'ProjectHooks', api: true do
   include ApiHelpers
   let(:user) { create(:user) }
   let(:user3) { create(:user) }
   let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
-  let!(:hook) { create(:project_hook, project: project, url: "http://example.com", push_events: true, merge_requests_events: true, tag_push_events: true, issues_events: true, note_events: true, enable_ssl_verification: true) }
+  let!(:hook) do
+    create(:project_hook,
+           project: project, url: "http://example.com",
+           push_events: true, merge_requests_events: true, tag_push_events: true,
+           issues_events: true, note_events: true, build_events: true,
+           enable_ssl_verification: true)
+  end
 
   before do
     project.team << [user, :master]
@@ -26,6 +32,7 @@ describe API::API, 'ProjectHooks', api: true  do
         expect(json_response.first['merge_requests_events']).to eq(true)
         expect(json_response.first['tag_push_events']).to eq(true)
         expect(json_response.first['note_events']).to eq(true)
+        expect(json_response.first['build_events']).to eq(true)
         expect(json_response.first['enable_ssl_verification']).to eq(true)
       end
     end
@@ -83,6 +90,7 @@ describe API::API, 'ProjectHooks', api: true  do
       expect(json_response['merge_requests_events']).to eq(false)
       expect(json_response['tag_push_events']).to eq(false)
       expect(json_response['note_events']).to eq(false)
+      expect(json_response['build_events']).to eq(false)
       expect(json_response['enable_ssl_verification']).to eq(true)
     end
 
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 9fc294118ae12e35fee731274c5c4dbcf16cbefa..7f0f9454b1006a193e5f10470e50857973af4a4b 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -65,6 +65,22 @@ describe API::API, api: true  do
         expect(json_response.first.keys).to include('tag_list')
       end
 
+      it 'should include open_issues_count' do
+        get api('/projects', user)
+        expect(response.status).to eq 200
+        expect(json_response).to be_an Array
+        expect(json_response.first.keys).to include('open_issues_count')
+      end
+
+      it 'should not include open_issues_count' do
+        project.update_attributes( { issues_enabled: false } )
+
+        get api('/projects', user)
+        expect(response.status).to eq 200
+        expect(json_response).to be_an Array
+        expect(json_response.first.keys).not_to include('open_issues_count')
+      end
+
       context 'and using search' do
         it 'should return searched project' do
           get api('/projects', user), { search: project.name }
@@ -86,18 +102,6 @@ describe API::API, api: true  do
           expect(json_response).to be_an Array
           expect(json_response.first['id']).to eq(project3.id)
         end
-
-        it 'returns projects in the correct order when ci_enabled_first parameter is passed' do
-          [project, project2, project3].each do |project|
-            project.builds_enabled = false
-            project.build_missing_services
-          end
-          project2.builds_enabled = true
-          get api('/projects', user), { ci_enabled_first: 'true' }
-          expect(response.status).to eq(200)
-          expect(json_response).to be_an Array
-          expect(json_response.first['id']).to eq(project2.id)
-        end
       end
     end
   end
@@ -127,6 +131,7 @@ describe API::API, api: true  do
 
         expect(json_response).to satisfy do |response|
           response.one? do |entry|
+            entry.has_key?('permissions') &&
             entry['name'] == project.name &&
               entry['owner']['username'] == user.username
           end
@@ -135,6 +140,25 @@ describe API::API, api: true  do
     end
   end
 
+  describe 'GET /projects/starred' do
+    before do
+      admin.starred_projects << project
+      admin.save!
+    end
+
+    it 'should return the starred projects' do
+      get api('/projects/all', admin)
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+
+      expect(json_response).to satisfy do |response|
+        response.one? do |entry|
+          entry['name'] == project.name
+        end
+      end
+    end
+  end
+
   describe 'POST /projects' do
     context 'maximum number of projects reached' do
       it 'should not create new project and respond with 403' do
@@ -359,6 +383,18 @@ describe API::API, api: true  do
     end
 
     describe 'permissions' do
+      context 'all projects' do
+        it 'Contains permission information' do
+          project.team << [user, :master]
+          get api("/projects", user)
+
+          expect(response.status).to eq(200)
+          expect(json_response.first['permissions']['project_access']['access_level']).
+              to eq(Gitlab::Access::MASTER)
+          expect(json_response.first['permissions']['group_access']).to be_nil
+        end
+      end
+
       context 'personal project' do
         it 'Sets project access and returns 200' do
           project.team << [user, :master]
@@ -389,14 +425,30 @@ describe API::API, api: true  do
   describe 'GET /projects/:id/events' do
     before { project_member2 }
 
-    it 'should return a project events' do
-      get api("/projects/#{project.id}/events", user)
-      expect(response.status).to eq(200)
-      json_event = json_response.first
+    context 'valid request' do
+      before do
+        note = create(:note_on_issue, note: 'What an awesome day!', project: project)
+        EventCreateService.new.leave_note(note, note.author)
+        get api("/projects/#{project.id}/events", user)
+      end
+
+      it { expect(response.status).to eq(200) }
+
+      context 'joined event' do
+        let(:json_event) { json_response[1] }
 
-      expect(json_event['action_name']).to eq('joined')
-      expect(json_event['project_id'].to_i).to eq(project.id)
-      expect(json_event['author_username']).to eq(user3.username)
+        it { expect(json_event['action_name']).to eq('joined') }
+        it { expect(json_event['project_id'].to_i).to eq(project.id) }
+        it { expect(json_event['author_username']).to eq(user3.username) }
+        it { expect(json_event['author']['name']).to eq(user3.name) }
+      end
+
+      context 'comment event' do
+        let(:json_event) { json_response.first }
+
+        it { expect(json_event['action_name']).to eq('commented on') }
+        it { expect(json_event['note']['body']).to eq('What an awesome day!') }
+      end
     end
 
     it 'should return a 404 error if not found' do
@@ -451,7 +503,7 @@ describe API::API, api: true  do
     end
   end
 
-  describe 'PUT /projects/:id/snippets/:shippet_id' do
+  describe 'PUT /projects/:id/snippets/:snippet_id' do
     it 'should update an existing project snippet' do
       put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
         code: 'updated code'
@@ -726,6 +778,18 @@ describe API::API, api: true  do
         end
       end
 
+      it 'should update visibility_level from public to private' do
+        project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
+
+        project_param = { public: false }
+        put api("/projects/#{project3.id}", user), project_param
+        expect(response.status).to eq(200)
+        project_param.each_pair do |k, v|
+          expect(json_response[k.to_s]).to eq(v)
+        end
+        expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
+      end
+
       it 'should not update name to existing name' do
         project_param = { name: project3.name }
         put api("/projects/#{project.id}", user), project_param
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index b180d2fec77b8fd52197589b5e392c90bd1bf6ba..fed9ae1949b419834f3f6d1593765e449f27ff3f 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -29,7 +29,7 @@ describe API::API, api: true  do
         if required_attributes.empty?
           expected_code = 200
         else
-          attrs.delete(required_attributes.shuffle.first)
+          attrs.delete(required_attributes.sample)
           expected_code = 400
         end
         
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..314bd7ddc59bfa3a8aae2853449ba282d945242f
--- /dev/null
+++ b/spec/requests/api/triggers_spec.rb
@@ -0,0 +1,80 @@
+require 'spec_helper'
+
+describe API::API do
+  include ApiHelpers
+
+  describe 'POST /projects/:project_id/trigger' do
+    let!(:trigger_token) { 'secure token' }
+    let!(:project) { FactoryGirl.create(:project) }
+    let!(:project2) { FactoryGirl.create(:empty_project) }
+    let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) }
+    let(:options) do
+      {
+        token: trigger_token
+      }
+    end
+
+    before do
+      stub_ci_commit_to_return_yaml_file
+    end
+
+    context 'Handles errors' do
+      it 'should return bad request if token is missing' do
+        post api("/projects/#{project.id}/trigger/builds"), ref: 'master'
+        expect(response.status).to eq(400)
+      end
+
+      it 'should return not found if project is not found' do
+        post api('/projects/0/trigger/builds'), options.merge(ref: 'master')
+        expect(response.status).to eq(404)
+      end
+
+      it 'should return unauthorized if token is for different project' do
+        post api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master')
+        expect(response.status).to eq(401)
+      end
+    end
+
+    context 'Have a commit' do
+      let(:commit) { project.ci_commits.last }
+
+      it 'should create builds' do
+        post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master')
+        expect(response.status).to eq(201)
+        commit.builds.reload
+        expect(commit.builds.size).to eq(2)
+      end
+
+      it 'should return bad request with no builds created if there\'s no commit for that ref' do
+        post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch')
+        expect(response.status).to eq(400)
+        expect(json_response['message']).to eq('No builds created')
+      end
+
+      context 'Validates variables' do
+        let(:variables) do
+          { 'TRIGGER_KEY' => 'TRIGGER_VALUE' }
+        end
+
+        it 'should validate variables to be a hash' do
+          post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master')
+          expect(response.status).to eq(400)
+          expect(json_response['message']).to eq('variables needs to be a hash')
+        end
+
+        it 'should validate variables needs to be a map of key-valued strings' do
+          post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master')
+          expect(response.status).to eq(400)
+          expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
+        end
+
+        it 'create trigger request with variables' do
+          post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master')
+          expect(response.status).to eq(201)
+          commit.builds.reload
+          expect(commit.builds.first.trigger_request.variables).to eq(variables)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index a9ef2fe58858000a39fff1f1e3eb89899b2845ac..4f278551d0741b8cf7001fbf2f39570c2ef5a5fb 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -27,6 +27,13 @@ describe API::API, api: true  do
           user['username'] == username
         end['username']).to eq(username)
       end
+
+      it "should return one user" do
+        get api("/users?username=#{omniauth_user.username}", user)
+        expect(response.status).to eq(200)
+        expect(json_response).to be_an Array
+        expect(json_response.first['username']).to eq(omniauth_user.username)
+      end
     end
 
     context "when admin" do
@@ -153,7 +160,7 @@ describe API::API, api: true  do
       expect(json_response['message']['projects_limit']).
         to eq(['must be greater than or equal to 0'])
       expect(json_response['message']['username']).
-        to eq([Gitlab::Regex.send(:namespace_regex_message)])
+        to eq([Gitlab::Regex.namespace_regex_message])
     end
 
     it "shouldn't available for non admin users" do
@@ -296,7 +303,7 @@ describe API::API, api: true  do
       expect(json_response['message']['projects_limit']).
         to eq(['must be greater than or equal to 0'])
       expect(json_response['message']['username']).
-        to eq([Gitlab::Regex.send(:namespace_regex_message)])
+        to eq([Gitlab::Regex.namespace_regex_message])
     end
 
     context "with existing user" do
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index c2be045099dc2614bb1f8afb4d7166816a082c12..c27e87c4acce8dc5aed72444ad19b9f57ba6e590 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -4,8 +4,7 @@ describe Ci::API::API do
   include ApiHelpers
 
   let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) }
-  let(:project) { FactoryGirl.create(:ci_project) }
-  let(:gl_project) { project.gl_project }
+  let(:project) { FactoryGirl.create(:empty_project) }
 
   before do
     stub_ci_commit_to_return_yaml_file
@@ -13,16 +12,15 @@ describe Ci::API::API do
 
   describe "Builds API for runners" do
     let(:shared_runner) { FactoryGirl.create(:ci_runner, token: "SharedRunner") }
-    let(:shared_project) { FactoryGirl.create(:ci_project, name: "SharedProject") }
-    let(:shared_gl_project) { shared_project.gl_project }
+    let(:shared_project) { FactoryGirl.create(:empty_project, name: "SharedProject") }
 
     before do
-      FactoryGirl.create :ci_runner_project, project_id: project.id, runner_id: runner.id
+      FactoryGirl.create :ci_runner_project, project: project, runner: runner
     end
 
     describe "POST /builds/register" do
       it "should start a build" do
-        commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
+        commit = FactoryGirl.create(:ci_commit, project: project)
         commit.create_builds('master', false, nil)
         build = commit.builds.first
 
@@ -40,7 +38,7 @@ describe Ci::API::API do
       end
 
       it "should return 404 error if no builds for specific runner" do
-        commit = FactoryGirl.create(:ci_commit, gl_project: shared_gl_project)
+        commit = FactoryGirl.create(:ci_commit, project: shared_project)
         FactoryGirl.create(:ci_build, commit: commit, status: 'pending')
 
         post ci_api("/builds/register"), token: runner.token
@@ -49,7 +47,7 @@ describe Ci::API::API do
       end
 
       it "should return 404 error if no builds for shared runner" do
-        commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
+        commit = FactoryGirl.create(:ci_commit, project: project)
         FactoryGirl.create(:ci_build, commit: commit, status: 'pending')
 
         post ci_api("/builds/register"), token: shared_runner.token
@@ -58,7 +56,7 @@ describe Ci::API::API do
       end
 
       it "returns options" do
-        commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
+        commit = FactoryGirl.create(:ci_commit, project: project)
         commit.create_builds('master', false, nil)
 
         post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -68,7 +66,7 @@ describe Ci::API::API do
       end
 
       it "returns variables" do
-        commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
+        commit = FactoryGirl.create(:ci_commit, project: project)
         commit.create_builds('master', false, nil)
         project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
 
@@ -85,7 +83,7 @@ describe Ci::API::API do
 
       it "returns variables for triggers" do
         trigger = FactoryGirl.create(:ci_trigger, project: project)
-        commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
+        commit = FactoryGirl.create(:ci_commit, project: project)
 
         trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger)
         commit.create_builds('master', false, nil, trigger_request)
@@ -106,7 +104,7 @@ describe Ci::API::API do
     end
 
     describe "PUT /builds/:id" do
-      let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project)}
+      let(:commit) { FactoryGirl.create(:ci_commit, project: project)}
       let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) }
 
       it "should update a running build" do
@@ -126,14 +124,14 @@ describe Ci::API::API do
     context "Artifacts" do
       let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
       let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
-      let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
+      let(:commit) { FactoryGirl.create(:ci_commit, project: project) }
       let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) }
       let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") }
       let(:post_url) { ci_api("/builds/#{build.id}/artifacts") }
       let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") }
       let(:get_url) { ci_api("/builds/#{build.id}/artifacts") }
       let(:headers) { { "GitLab-Workhorse" => "1.0" } }
-      let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.project.token) }
+      let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) }
 
       describe "POST /builds/:id/artifacts/authorize" do
         context "should authorize posting artifact to running build" do
@@ -142,7 +140,7 @@ describe Ci::API::API do
           end
 
           it "using token as parameter" do
-            post authorize_url, { token: build.project.token }, headers
+            post authorize_url, { token: build.token }, headers
             expect(response.status).to eq(200)
             expect(json_response["TempPath"]).to_not be_nil
           end
@@ -161,7 +159,7 @@ describe Ci::API::API do
 
           it "using token as parameter" do
             stub_application_setting(max_artifacts_size: 0)
-            post authorize_url, { token: build.project.token, filesize: 100 }, headers
+            post authorize_url, { token: build.token, filesize: 100 }, headers
             expect(response.status).to eq(413)
           end
 
@@ -241,7 +239,7 @@ describe Ci::API::API do
             end
 
             it do
-              post post_url, { token: build.project.token }, {}
+              post post_url, { token: build.token }, {}
               expect(response.status).to eq(403)
             end
           end
@@ -281,12 +279,12 @@ describe Ci::API::API do
       describe "DELETE /builds/:id/artifacts" do
         before do
           build.run!
-          post delete_url, token: build.project.token, file: file_upload
+          post delete_url, token: build.token, file: file_upload
         end
 
         it "should delete artifact build" do
           build.success
-          delete delete_url, token: build.project.token
+          delete delete_url, token: build.token
           expect(response.status).to eq(200)
         end
       end
@@ -298,12 +296,12 @@ describe Ci::API::API do
 
         it "should download artifact" do
           build.update_attributes(artifacts_file: file_upload)
-          get get_url, token: build.project.token
+          get get_url, token: build.token
           expect(response.status).to eq(200)
         end
 
         it "should fail to download if no artifact uploaded" do
-          get get_url, token: build.project.token
+          get get_url, token: build.token
           expect(response.status).to eq(404)
         end
       end
diff --git a/spec/requests/ci/api/commits_spec.rb b/spec/requests/ci/api/commits_spec.rb
deleted file mode 100644
index aa51ba95bcadc1a545bbe9c9a12d5f271a64af1c..0000000000000000000000000000000000000000
--- a/spec/requests/ci/api/commits_spec.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-require 'spec_helper'
-
-describe Ci::API::API, 'Commits' do
-  include ApiHelpers
-
-  let(:project) { FactoryGirl.create(:ci_project) }
-  let(:gl_project) { project.gl_project }
-  let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
-
-  let(:options) do
-    {
-      project_token: project.token,
-      project_id: project.id
-    }
-  end
-
-  describe "GET /commits" do
-    before { commit }
-
-    it "should return commits per project" do
-      get ci_api("/commits"), options
-
-      expect(response.status).to eq(200)
-      expect(json_response.count).to eq(1)
-      expect(json_response.first["project_id"]).to eq(project.id)
-      expect(json_response.first["sha"]).to eq(commit.sha)
-    end
-  end
-
-  describe "POST /commits" do
-    let(:data) do
-      {
-        "before" => "95790bf891e76fee5e1747ab589903a6a1f80f22",
-        "after" => "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
-        "ref" => "refs/heads/master",
-        "commits" => [
-          {
-            "id" => "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
-            "message" => "Update Catalan translation to e38cb41.",
-            "timestamp" => "2011-12-12T14:27:31+02:00",
-            "url" => "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
-            "author" => {
-              "name" => "Jordi Mallach",
-              "email" => "jordi@softcatala.org",
-            }
-          }
-        ]
-      }
-    end
-
-    it "should create a build" do
-      post ci_api("/commits"), options.merge(data: data)
-
-      expect(response.status).to eq(201)
-      expect(json_response['sha']).to eq("da1560886d4f094c3e6c9ef40349f7d38b5d27d7")
-    end
-
-    it "should return 400 error if no data passed" do
-      post ci_api("/commits"), options
-
-      expect(response.status).to eq(400)
-      expect(json_response['message']).to eq("400 (Bad request) \"data\" not given")
-    end
-  end
-end
diff --git a/spec/requests/ci/api/projects_spec.rb b/spec/requests/ci/api/projects_spec.rb
deleted file mode 100644
index 893fd168d3e9c9cf90b40d633fdd714c3221a6be..0000000000000000000000000000000000000000
--- a/spec/requests/ci/api/projects_spec.rb
+++ /dev/null
@@ -1,232 +0,0 @@
-require 'spec_helper'
-
-describe Ci::API::API do
-  include ApiHelpers
-
-  let(:gitlab_url) { GitlabCi.config.gitlab_ci.url }
-  let(:user) { create(:user) }
-  let(:private_token) { user.private_token }
-
-  let(:options) do
-    {
-      private_token: private_token,
-      url: gitlab_url
-    }
-  end
-
-  before do
-    stub_gitlab_calls
-  end
-
-  context "requests for scoped projects" do
-    # NOTE: These ids are tied to the actual projects on demo.gitlab.com
-    describe "GET /projects" do
-      let!(:project1) { FactoryGirl.create(:ci_project) }
-      let!(:project2) { FactoryGirl.create(:ci_project) }
-
-      before do
-        project1.gl_project.team << [user, :developer]
-        project2.gl_project.team << [user, :developer]
-      end
-
-      it "should return all projects on the CI instance" do
-        get ci_api("/projects"), options
-        expect(response.status).to eq(200)
-        expect(json_response.count).to eq(2)
-        expect(json_response.first["id"]).to eq(project1.id)
-        expect(json_response.last["id"]).to eq(project2.id)
-      end
-    end
-
-    describe "GET /projects/owned" do
-      let!(:gl_project1) {FactoryGirl.create(:empty_project, namespace: user.namespace)}
-      let!(:gl_project2) {FactoryGirl.create(:empty_project, namespace: user.namespace)}
-      let!(:project1) { gl_project1.ensure_gitlab_ci_project }
-      let!(:project2) { gl_project2.ensure_gitlab_ci_project }
-
-      before do
-        project1.gl_project.team << [user, :developer]
-        project2.gl_project.team << [user, :developer]
-      end
-
-      it "should return all projects on the CI instance" do
-        get ci_api("/projects/owned"), options
-
-        expect(response.status).to eq(200)
-        expect(json_response.count).to eq(2)
-      end
-    end
-  end
-
-  describe "POST /projects/:project_id/webhooks" do
-    let!(:project) { FactoryGirl.create(:ci_project) }
-
-    context "Valid Webhook URL" do
-      let!(:webhook) { { web_hook: "http://example.com/sth/1/ala_ma_kota" } }
-
-      before do
-        options.merge!(webhook)
-      end
-
-      it "should create webhook for specified project" do
-        project.gl_project.team << [user, :master]
-        post ci_api("/projects/#{project.id}/webhooks"), options
-        expect(response.status).to eq(201)
-        expect(json_response["url"]).to eq(webhook[:web_hook])
-      end
-
-      it "fails to create webhook for non existsing project" do
-        post ci_api("/projects/non-existant-id/webhooks"), options
-        expect(response.status).to eq(404)
-      end
-
-      it "non-manager is not authorized" do
-        post ci_api("/projects/#{project.id}/webhooks"), options
-        expect(response.status).to eq(401)
-      end
-    end
-
-    context "Invalid Webhook URL" do
-      let!(:webhook) { { web_hook: "ala_ma_kota" } }
-
-      before do
-        options.merge!(webhook)
-      end
-
-      it "fails to create webhook for not valid url" do
-        project.gl_project.team << [user, :master]
-        post ci_api("/projects/#{project.id}/webhooks"), options
-        expect(response.status).to eq(400)
-      end
-    end
-
-    context "Missed web_hook parameter" do
-      it "fails to create webhook for not provided url" do
-        project.gl_project.team << [user, :master]
-        post ci_api("/projects/#{project.id}/webhooks"), options
-        expect(response.status).to eq(400)
-      end
-    end
-  end
-
-  describe "GET /projects/:id" do
-    let!(:project) { FactoryGirl.create(:ci_project) }
-
-    before do
-      project.gl_project.team << [user, :developer]
-    end
-
-    context "with an existing project" do
-      it "should retrieve the project info" do
-        get ci_api("/projects/#{project.id}"), options
-        expect(response.status).to eq(200)
-        expect(json_response['id']).to eq(project.id)
-      end
-    end
-
-    context "with a non-existing project" do
-      it "should return 404 error if project not found" do
-        get ci_api("/projects/non_existent_id"), options
-        expect(response.status).to eq(404)
-      end
-    end
-  end
-
-  describe "PUT /projects/:id" do
-    let!(:project) { FactoryGirl.create(:ci_project) }
-    let!(:project_info) { { default_ref: "develop" } }
-
-    before do
-      options.merge!(project_info)
-    end
-
-    it "should update a specific project's information" do
-      project.gl_project.team << [user, :master]
-      put ci_api("/projects/#{project.id}"), options
-      expect(response.status).to eq(200)
-      expect(json_response["default_ref"]).to eq(project_info[:default_ref])
-    end
-
-    it "fails to update a non-existing project" do
-      put ci_api("/projects/non-existant-id"), options
-      expect(response.status).to eq(404)
-    end
-
-    it "non-manager is not authorized" do
-      put ci_api("/projects/#{project.id}"), options
-      expect(response.status).to eq(401)
-    end
-  end
-
-  describe "DELETE /projects/:id" do
-    let!(:project) { FactoryGirl.create(:ci_project) }
-
-    it "should delete a specific project" do
-      project.gl_project.team << [user, :master]
-      delete ci_api("/projects/#{project.id}"), options
-      expect(response.status).to eq(200)
-      expect { project.reload }.
-        to raise_error(ActiveRecord::RecordNotFound)
-    end
-
-    it "non-manager is not authorized" do
-      delete ci_api("/projects/#{project.id}"), options
-      expect(response.status).to eq(401)
-    end
-
-    it "is getting not found error" do
-      delete ci_api("/projects/not-existing_id"), options
-      expect(response.status).to eq(404)
-    end
-  end
-
-  describe "POST /projects/:id/runners/:id" do
-    let(:project) { FactoryGirl.create(:ci_project) }
-    let(:runner) { FactoryGirl.create(:ci_runner) }
-
-    it "should add the project to the runner" do
-      project.gl_project.team << [user, :master]
-      post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options
-      expect(response.status).to eq(201)
-
-      project.reload
-      expect(project.runners.first.id).to eq(runner.id)
-    end
-
-    it "should fail if it tries to link a non-existing project or runner" do
-      post ci_api("/projects/#{project.id}/runners/non-existing"), options
-      expect(response.status).to eq(404)
-
-      post ci_api("/projects/non-existing/runners/#{runner.id}"), options
-      expect(response.status).to eq(404)
-    end
-
-    it "non-manager is not authorized" do
-      allow_any_instance_of(User).to receive(:can_manage_project?).and_return(false)
-      post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options
-      expect(response.status).to eq(401)
-    end
-  end
-
-  describe "DELETE /projects/:id/runners/:id" do
-    let(:project) { FactoryGirl.create(:ci_project) }
-    let(:runner) { FactoryGirl.create(:ci_runner) }
-
-    it "should remove the project from the runner" do
-      project.gl_project.team << [user, :master]
-      post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options
-
-      expect(project.runners).to be_present
-      delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options
-      expect(response.status).to eq(200)
-
-      project.reload
-      expect(project.runners).to be_empty
-    end
-
-    it "non-manager is not authorized" do
-      delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options
-      expect(response.status).to eq(401)
-    end
-  end
-end
diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb
index 11dc089e1f55961273334eabc365d5a36f80f687..5942aa7a1b59f27cade4f9b0fe08018bd0a76ea4 100644
--- a/spec/requests/ci/api/runners_spec.rb
+++ b/spec/requests/ci/api/runners_spec.rb
@@ -4,57 +4,37 @@ describe Ci::API::API do
   include ApiHelpers
   include StubGitlabCalls
 
+  let(:registration_token) { 'abcdefg123456' }
+
   before do
     stub_gitlab_calls
-  end
-
-  describe "GET /runners" do
-    let(:gitlab_url) { GitlabCi.config.gitlab_ci.url }
-    let(:private_token) { create(:user).private_token }
-    let(:options) do
-      {
-        private_token: private_token,
-        url: gitlab_url
-      }
-    end
-
-    before do
-      5.times { FactoryGirl.create(:ci_runner) }
-    end
-
-    it "should retrieve a list of all runners" do
-      get ci_api("/runners", nil), options
-      expect(response.status).to eq(200)
-      expect(json_response.count).to eq(5)
-      expect(json_response.last).to have_key("id")
-      expect(json_response.last).to have_key("token")
-    end
+    stub_application_setting(runners_registration_token: registration_token)
   end
 
   describe "POST /runners/register" do
     describe "should create a runner if token provided" do
-      before { post ci_api("/runners/register"), token: GitlabCi::REGISTRATION_TOKEN }
+      before { post ci_api("/runners/register"), token: registration_token }
 
       it { expect(response.status).to eq(201) }
     end
 
     describe "should create a runner with description" do
-      before { post ci_api("/runners/register"), token: GitlabCi::REGISTRATION_TOKEN, description: "server.hostname" }
+      before { post ci_api("/runners/register"), token: registration_token, description: "server.hostname" }
 
       it { expect(response.status).to eq(201) }
       it { expect(Ci::Runner.first.description).to eq("server.hostname") }
     end
 
     describe "should create a runner with tags" do
-      before { post ci_api("/runners/register"), token: GitlabCi::REGISTRATION_TOKEN, tag_list: "tag1, tag2" }
+      before { post ci_api("/runners/register"), token: registration_token, tag_list: "tag1, tag2" }
 
       it { expect(response.status).to eq(201) }
       it { expect(Ci::Runner.first.tag_list.sort).to eq(["tag1", "tag2"]) }
     end
 
     describe "should create a runner if project token provided" do
-      let(:project) { FactoryGirl.create(:ci_project) }
-      before { post ci_api("/runners/register"), token: project.token }
+      let(:project) { FactoryGirl.create(:empty_project) }
+      before { post ci_api("/runners/register"), token: project.runners_token }
 
       it { expect(response.status).to eq(201) }
       it { expect(project.runners.size).to eq(1) }
diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb
index a2b436d58119afc69f80b8affc9bc0f1c4c85a9e..0ef03f9371b4db99927355c5edd886db69d757d9 100644
--- a/spec/requests/ci/api/triggers_spec.rb
+++ b/spec/requests/ci/api/triggers_spec.rb
@@ -5,9 +5,8 @@ describe Ci::API::API do
 
   describe 'POST /projects/:project_id/refs/:ref/trigger' do
     let!(:trigger_token) { 'secure token' }
-    let!(:gl_project) { FactoryGirl.create(:project) }
-    let!(:project) { gl_project.ensure_gitlab_ci_project }
-    let!(:project2) { FactoryGirl.create(:ci_project) }
+    let!(:project) { FactoryGirl.create(:project, ci_id: 10) }
+    let!(:project2) { FactoryGirl.create(:empty_project, ci_id: 11) }
     let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) }
     let(:options) do
       {
@@ -21,7 +20,7 @@ describe Ci::API::API do
 
     context 'Handles errors' do
       it 'should return bad request if token is missing' do
-        post ci_api("/projects/#{project.id}/refs/master/trigger")
+        post ci_api("/projects/#{project.ci_id}/refs/master/trigger")
         expect(response.status).to eq(400)
       end
 
@@ -31,23 +30,23 @@ describe Ci::API::API do
       end
 
       it 'should return unauthorized if token is for different project' do
-        post ci_api("/projects/#{project2.id}/refs/master/trigger"), options
+        post ci_api("/projects/#{project2.ci_id}/refs/master/trigger"), options
         expect(response.status).to eq(401)
       end
     end
 
     context 'Have a commit' do
-      let(:commit) { project.commits.last }
+      let(:commit) { project.ci_commits.last }
 
       it 'should create builds' do
-        post ci_api("/projects/#{project.id}/refs/master/trigger"), options
+        post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options
         expect(response.status).to eq(201)
         commit.builds.reload
         expect(commit.builds.size).to eq(2)
       end
 
       it 'should return bad request with no builds created if there\'s no commit for that ref' do
-        post ci_api("/projects/#{project.id}/refs/other-branch/trigger"), options
+        post ci_api("/projects/#{project.ci_id}/refs/other-branch/trigger"), options
         expect(response.status).to eq(400)
         expect(json_response['message']).to eq('No builds created')
       end
@@ -58,19 +57,19 @@ describe Ci::API::API do
         end
 
         it 'should validate variables to be a hash' do
-          post ci_api("/projects/#{project.id}/refs/master/trigger"), options.merge(variables: 'value')
+          post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: 'value')
           expect(response.status).to eq(400)
           expect(json_response['message']).to eq('variables needs to be a hash')
         end
 
         it 'should validate variables needs to be a map of key-valued strings' do
-          post ci_api("/projects/#{project.id}/refs/master/trigger"), options.merge(variables: { key: %w(1 2) })
+          post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: { key: %w(1 2) })
           expect(response.status).to eq(400)
           expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
         end
 
         it 'create trigger request with variables' do
-          post ci_api("/projects/#{project.id}/refs/master/trigger"), options.merge(variables: variables)
+          post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: variables)
           expect(response.status).to eq(201)
           commit.builds.reload
           expect(commit.builds.first.trigger_request.variables).to eq(variables)
diff --git a/spec/services/archive_repository_service_spec.rb b/spec/services/archive_repository_service_spec.rb
index f7a36cd96705025a15ba2ce40b15a19f37cdc3fb..bd871605c660851c82089705e2e7254da4897f16 100644
--- a/spec/services/archive_repository_service_spec.rb
+++ b/spec/services/archive_repository_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe ArchiveRepositoryService do
+describe ArchiveRepositoryService, services: true do
   let(:project) { create(:project) }
   subject { ArchiveRepositoryService.new(project, "master", "zip") }
 
diff --git a/spec/services/ci/create_commit_service_spec.rb b/spec/services/ci/create_commit_service_spec.rb
deleted file mode 100644
index e0ede1d58b71a300b8902afcadfe39e59c892ae1..0000000000000000000000000000000000000000
--- a/spec/services/ci/create_commit_service_spec.rb
+++ /dev/null
@@ -1,172 +0,0 @@
-require 'spec_helper'
-
-module Ci
-  describe CreateCommitService do
-    let(:service) { CreateCommitService.new }
-    let(:project) { FactoryGirl.create(:ci_project) }
-    let(:user) { nil }
-
-    before do
-      stub_ci_commit_to_return_yaml_file
-    end
-
-    describe :execute do
-      context 'valid params' do
-        let(:commit) do
-          service.execute(project, user,
-            ref: 'refs/heads/master',
-            before: '00000000',
-            after: '31das312',
-            commits: [ { message: "Message" } ]
-          )
-        end
-
-        it { expect(commit).to be_kind_of(Commit) }
-        it { expect(commit).to be_valid }
-        it { expect(commit).to be_persisted }
-        it { expect(commit).to eq(project.commits.last) }
-        it { expect(commit.builds.first).to be_kind_of(Build) }
-      end
-
-      context "skip tag if there is no build for it" do
-        it "creates commit if there is appropriate job" do
-          result = service.execute(project, user,
-            ref: 'refs/tags/0_1',
-            before: '00000000',
-            after: '31das312',
-            commits: [ { message: "Message" } ]
-          )
-          expect(result).to be_persisted
-        end
-
-        it "creates commit if there is no appropriate job but deploy job has right ref setting" do
-          config = YAML.dump({ deploy: { deploy: "ls", only: ["0_1"] } })
-          stub_ci_commit_yaml_file(config)
-
-          result = service.execute(project, user,
-            ref: 'refs/heads/0_1',
-            before: '00000000',
-            after: '31das312',
-            commits: [ { message: "Message" } ]
-          )
-          expect(result).to be_persisted
-        end
-      end
-
-      it 'skips commits without .gitlab-ci.yml' do
-        stub_ci_commit_yaml_file(nil)
-        result = service.execute(project, user,
-                                 ref: 'refs/heads/0_1',
-                                 before: '00000000',
-                                 after: '31das312',
-                                 commits: [ { message: 'Message' } ]
-        )
-        expect(result).to be_persisted
-        expect(result.builds.any?).to be_falsey
-        expect(result.status).to eq('skipped')
-        expect(result.yaml_errors).to be_nil
-      end
-
-      it 'skips commits if yaml is invalid' do
-        message = 'message'
-        allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
-        stub_ci_commit_yaml_file('invalid: file: file')
-        commits = [{ message: message }]
-        commit = service.execute(project, user,
-                                 ref: 'refs/tags/0_1',
-                                 before: '00000000',
-                                 after: '31das312',
-                                 commits: commits
-        )
-        expect(commit.builds.any?).to be false
-        expect(commit.status).to eq('failed')
-        expect(commit.yaml_errors).to_not be_nil
-      end
-
-      describe :ci_skip? do
-        let(:message) { "some message[ci skip]" }
-
-        before do
-          allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
-        end
-
-        it "skips builds creation if there is [ci skip] tag in commit message" do
-          commits = [{ message: message }]
-          commit = service.execute(project, user,
-            ref: 'refs/tags/0_1',
-            before: '00000000',
-            after: '31das312',
-            commits: commits
-          )
-          expect(commit.builds.any?).to be false
-          expect(commit.status).to eq("skipped")
-        end
-
-        it "does not skips builds creation if there is no [ci skip] tag in commit message" do
-          allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { "some message" }
-
-          commits = [{ message: "some message" }]
-          commit = service.execute(project, user,
-            ref: 'refs/tags/0_1',
-            before: '00000000',
-            after: '31das312',
-            commits: commits
-          )
-
-          expect(commit.builds.first.name).to eq("staging")
-        end
-
-        it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do
-          stub_ci_commit_yaml_file('invalid: file: fiile')
-          commits = [{ message: message }]
-          commit = service.execute(project, user,
-                                   ref: 'refs/tags/0_1',
-                                   before: '00000000',
-                                   after: '31das312',
-                                   commits: commits
-          )
-          expect(commit.builds.any?).to be false
-          expect(commit.status).to eq("skipped")
-          expect(commit.yaml_errors).to be_nil
-        end
-      end
-
-      it "skips build creation if there are already builds" do
-        allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { gitlab_ci_yaml }
-
-        commits = [{ message: "message" }]
-        commit = service.execute(project, user,
-          ref: 'refs/heads/master',
-          before: '00000000',
-          after: '31das312',
-          commits: commits
-        )
-        expect(commit.builds.count(:all)).to  eq(2)
-
-        commit = service.execute(project, user,
-          ref: 'refs/heads/master',
-          before: '00000000',
-          after: '31das312',
-          commits: commits
-        )
-        expect(commit.builds.count(:all)).to eq(2)
-      end
-
-      it "creates commit with failed status if yaml is invalid" do
-        stub_ci_commit_yaml_file('invalid: file')
-
-        commits = [{ message: "some message" }]
-
-        commit = service.execute(project, user,
-                                 ref: 'refs/tags/0_1',
-                                 before: '00000000',
-                                 after: '31das312',
-                                 commits: commits
-        )
-
-        expect(commit.status).to eq("failed")
-        expect(commit.builds.any?).to be false
-      end
-    end
-  end
-end
diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb
index 2ef4bb50a57d0edb5745b4d37939961fa050c27c..dbdc5370bd8124a61d0b259a2f84ef368ff3f734 100644
--- a/spec/services/ci/create_trigger_request_service_spec.rb
+++ b/spec/services/ci/create_trigger_request_service_spec.rb
@@ -1,9 +1,8 @@
 require 'spec_helper'
 
-describe Ci::CreateTriggerRequestService do
+describe Ci::CreateTriggerRequestService, services: true do
   let(:service) { Ci::CreateTriggerRequestService.new }
-  let(:gl_project) { create(:project) }
-  let(:project) { gl_project.ensure_gitlab_ci_project }
+  let(:project) { create(:project) }
   let(:trigger) { create(:ci_trigger, project: project) }
 
   before do
@@ -29,7 +28,7 @@ describe Ci::CreateTriggerRequestService do
 
       before do
         stub_ci_commit_yaml_file('{}')
-        FactoryGirl.create :ci_commit, gl_project: gl_project
+        FactoryGirl.create :ci_commit, project: project
       end
 
       it { expect(subject).to be_nil }
diff --git a/spec/services/ci/event_service_spec.rb b/spec/services/ci/event_service_spec.rb
deleted file mode 100644
index 1264e17ff5ecea89f36d1b6d3b845f507435d209..0000000000000000000000000000000000000000
--- a/spec/services/ci/event_service_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe Ci::EventService do
-  let(:project) { FactoryGirl.create :ci_project }
-  let(:user)   { double(username: "root", id: 1) }
-
-  before do
-    Event.destroy_all
-  end
-
-  describe :remove_project do
-    it "creates event" do
-      Ci::EventService.new.remove_project(user, project)
-
-      expect(Ci::Event.admin.last.description).to eq("Project \"#{project.name_with_namespace}\" has been removed by root")
-    end
-  end
-
-  describe :create_project do
-    it "creates event" do
-      Ci::EventService.new.create_project(user, project)
-
-      expect(Ci::Event.admin.last.description).to eq("Project \"#{project.name_with_namespace}\" has been created by root")
-    end
-  end
-
-  describe :change_project_settings do
-    it "creates event" do
-      Ci::EventService.new.change_project_settings(user, project)
-
-      expect(Ci::Event.last.description).to eq("User \"root\" updated projects settings")
-    end
-  end
-end
diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb
index cda7d0c4a518da4fa9acc14d18ca2cb005e12412..870861ad20a8cade2758bffe0a7386c2ae159eb3 100644
--- a/spec/services/ci/image_for_build_service_spec.rb
+++ b/spec/services/ci/image_for_build_service_spec.rb
@@ -1,18 +1,18 @@
 require 'spec_helper'
 
 module Ci
-  describe ImageForBuildService do
+  describe ImageForBuildService, services: true do
     let(:service) { ImageForBuildService.new }
-    let(:project) { FactoryGirl.create(:ci_project) }
-    let(:gl_project) { FactoryGirl.create(:project, gitlab_ci_project: project) }
-    let(:commit_sha) { gl_project.commit('master').sha }
-    let(:commit) { gl_project.ensure_ci_commit(commit_sha) }
+    let(:project) { FactoryGirl.create(:empty_project) }
+    let(:commit_sha) { '01234567890123456789' }
+    let(:commit) { project.ensure_ci_commit(commit_sha) }
     let(:build) { FactoryGirl.create(:ci_build, commit: commit) }
 
     describe :execute do
       before { build }
 
       context 'branch name' do
+        before { allow(project).to receive(:commit).and_return(OpenStruct.new(sha: commit_sha)) }
         before { build.run! }
         let(:image) { service.execute(project, ref: 'master') }
 
diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb
index b370dfbe1136ce891a01e90ad04c9a021f4cfa84..e81f9e757ac713a6fa69159e9756232ff8375ac5 100644
--- a/spec/services/ci/register_build_service_spec.rb
+++ b/spec/services/ci/register_build_service_spec.rb
@@ -1,16 +1,16 @@
 require 'spec_helper'
 
 module Ci
-  describe RegisterBuildService do
+  describe RegisterBuildService, services: true do
     let!(:service) { RegisterBuildService.new }
-    let!(:gl_project) { FactoryGirl.create :empty_project }
-    let!(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
+    let!(:project) { FactoryGirl.create :empty_project, shared_runners_enabled: false }
+    let!(:commit) { FactoryGirl.create :ci_commit, project: project }
     let!(:pending_build) { FactoryGirl.create :ci_build, commit: commit }
     let!(:shared_runner) { FactoryGirl.create(:ci_runner, is_shared: true) }
     let!(:specific_runner) { FactoryGirl.create(:ci_runner, is_shared: false) }
 
     before do
-      specific_runner.assign_to(gl_project.ensure_gitlab_ci_project)
+      specific_runner.assign_to(project)
     end
 
     describe :execute do
@@ -47,7 +47,7 @@ module Ci
 
       context 'allow shared runners' do
         before do
-          gl_project.gitlab_ci_project.update(shared_runners_enabled: true)
+          project.update(shared_runners_enabled: true)
         end
 
         context 'shared runner' do
@@ -71,7 +71,7 @@ module Ci
 
       context 'disallow shared runners' do
         before do
-          gl_project.gitlab_ci_project.update(shared_runners_enabled: false)
+          project.update(shared_runners_enabled: false)
         end
 
         context 'shared runner' do
diff --git a/spec/services/ci/web_hook_service_spec.rb b/spec/services/ci/web_hook_service_spec.rb
deleted file mode 100644
index aa48fcbcbfd4b5f79fed6da3d9253e94614c4bca..0000000000000000000000000000000000000000
--- a/spec/services/ci/web_hook_service_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'spec_helper'
-
-describe Ci::WebHookService do
-  let(:project) { FactoryGirl.create :ci_project }
-  let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
-  let(:commit)  { FactoryGirl.create :ci_commit, gl_project: gl_project }
-  let(:build)   { FactoryGirl.create :ci_build, commit: commit }
-  let(:hook)    { FactoryGirl.create :ci_web_hook, project: project }
-
-  describe :execute do
-    it "should execute successfully" do
-      stub_request(:post, hook.url).to_return(status: 200)
-      expect(Ci::WebHookService.new.build_end(build)).to be_truthy
-    end
-  end
-
-  context 'build_data' do
-    it "contains all needed fields" do
-      expect(build_data(build)).to include(
-        :build_id,
-        :project_id,
-        :ref,
-        :build_status,
-        :build_started_at,
-        :build_finished_at,
-        :before_sha,
-        :project_name,
-        :gitlab_url,
-        :build_name
-      )
-    end
-  end
-
-  def build_data(build)
-    Ci::WebHookService.new.send :build_data, build
-  end
-end
diff --git a/spec/services/create_commit_builds_service_spec.rb b/spec/services/create_commit_builds_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ea5dcfa068a0ffe114ebc1af7d3c5251b53da1ad
--- /dev/null
+++ b/spec/services/create_commit_builds_service_spec.rb
@@ -0,0 +1,175 @@
+require 'spec_helper'
+
+describe CreateCommitBuildsService, services: true do
+  let(:service) { CreateCommitBuildsService.new }
+  let(:project) { FactoryGirl.create(:empty_project) }
+  let(:user) { nil }
+
+  before do
+    stub_ci_commit_to_return_yaml_file
+  end
+
+  describe :execute do
+    context 'valid params' do
+      let(:commit) do
+        service.execute(project, user,
+                        ref: 'refs/heads/master',
+                        before: '00000000',
+                        after: '31das312',
+                        commits: [{ message: "Message" }]
+                       )
+      end
+
+      it { expect(commit).to be_kind_of(Ci::Commit) }
+      it { expect(commit).to be_valid }
+      it { expect(commit).to be_persisted }
+      it { expect(commit).to eq(project.ci_commits.last) }
+      it { expect(commit.builds.first).to be_kind_of(Ci::Build) }
+    end
+
+    context "skip tag if there is no build for it" do
+      it "creates commit if there is appropriate job" do
+        result = service.execute(project, user,
+                                 ref: 'refs/tags/0_1',
+                                 before: '00000000',
+                                 after: '31das312',
+                                 commits: [{ message: "Message" }]
+                                )
+        expect(result).to be_persisted
+      end
+
+      it "creates commit if there is no appropriate job but deploy job has right ref setting" do
+        config = YAML.dump({ deploy: { deploy: "ls", only: ["0_1"] } })
+        stub_ci_commit_yaml_file(config)
+
+        result = service.execute(project, user,
+                                 ref: 'refs/heads/0_1',
+                                 before: '00000000',
+                                 after: '31das312',
+                                 commits: [{ message: "Message" }]
+                                )
+        expect(result).to be_persisted
+      end
+    end
+
+    it 'skips creating ci_commit for refs without .gitlab-ci.yml' do
+      stub_ci_commit_yaml_file(nil)
+      result = service.execute(project, user,
+                               ref: 'refs/heads/0_1',
+                               before: '00000000',
+                               after: '31das312',
+                               commits: [{ message: 'Message' }]
+                              )
+      expect(result).to be_falsey
+      expect(Ci::Commit.count).to eq(0)
+    end
+
+    it 'fails commits if yaml is invalid' do
+      message = 'message'
+      allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
+      stub_ci_commit_yaml_file('invalid: file: file')
+      commits = [{ message: message }]
+      commit = service.execute(project, user,
+                               ref: 'refs/tags/0_1',
+                               before: '00000000',
+                               after: '31das312',
+                               commits: commits
+                              )
+      expect(commit).to be_persisted
+      expect(commit.builds.any?).to be false
+      expect(commit.status).to eq('failed')
+      expect(commit.yaml_errors).to_not be_nil
+    end
+
+    describe :ci_skip? do
+      let(:message) { "some message[ci skip]" }
+
+      before do
+        allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
+      end
+
+      it "skips builds creation if there is [ci skip] tag in commit message" do
+        commits = [{ message: message }]
+        commit = service.execute(project, user,
+                                 ref: 'refs/tags/0_1',
+                                 before: '00000000',
+                                 after: '31das312',
+                                 commits: commits
+                                )
+        expect(commit).to be_persisted
+        expect(commit.builds.any?).to be false
+        expect(commit.status).to eq("skipped")
+      end
+
+      it "does not skips builds creation if there is no [ci skip] tag in commit message" do
+        allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { "some message" }
+
+        commits = [{ message: "some message" }]
+        commit = service.execute(project, user,
+                                 ref: 'refs/tags/0_1',
+                                 before: '00000000',
+                                 after: '31das312',
+                                 commits: commits
+                                )
+
+        expect(commit).to be_persisted
+        expect(commit.builds.first.name).to eq("staging")
+      end
+
+      it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do
+        stub_ci_commit_yaml_file('invalid: file: fiile')
+        commits = [{ message: message }]
+        commit = service.execute(project, user,
+                                 ref: 'refs/tags/0_1',
+                                 before: '00000000',
+                                 after: '31das312',
+                                 commits: commits
+                                )
+        expect(commit).to be_persisted
+        expect(commit.builds.any?).to be false
+        expect(commit.status).to eq("skipped")
+        expect(commit.yaml_errors).to be_nil
+      end
+    end
+
+    it "skips build creation if there are already builds" do
+      allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { gitlab_ci_yaml }
+
+      commits = [{ message: "message" }]
+      commit = service.execute(project, user,
+                               ref: 'refs/heads/master',
+                               before: '00000000',
+                               after: '31das312',
+                               commits: commits
+                              )
+      expect(commit).to be_persisted
+      expect(commit.builds.count(:all)).to eq(2)
+
+      commit = service.execute(project, user,
+                               ref: 'refs/heads/master',
+                               before: '00000000',
+                               after: '31das312',
+                               commits: commits
+                              )
+      expect(commit).to be_persisted
+      expect(commit.builds.count(:all)).to eq(2)
+    end
+
+    it "creates commit with failed status if yaml is invalid" do
+      stub_ci_commit_yaml_file('invalid: file')
+
+      commits = [{ message: "some message" }]
+
+      commit = service.execute(project, user,
+                               ref: 'refs/tags/0_1',
+                               before: '00000000',
+                               after: '31das312',
+                               commits: commits
+                              )
+
+      expect(commit).to be_persisted
+      expect(commit.status).to eq("failed")
+      expect(commit.builds.any?).to be false
+    end
+  end
+end
diff --git a/spec/services/create_release_service_spec.rb b/spec/services/create_release_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..61e5ae72f5106ecc2a2d0efec36d728ab2f8e391
--- /dev/null
+++ b/spec/services/create_release_service_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe CreateReleaseService, services: true do
+  let(:project) { create(:project) }
+  let(:user) { create(:user) }
+  let(:tag_name) { project.repository.tag_names.first }
+  let(:description) { 'Awesome release!' }
+  let(:service) { CreateReleaseService.new(project, user) }
+
+  it 'creates a new release' do
+    result = service.execute(tag_name, description)
+    expect(result[:status]).to eq(:success)
+    release = project.releases.find_by(tag: tag_name)
+    expect(release).not_to be_nil
+    expect(release.description).to eq(description)
+  end
+
+  it 'raises an error if the tag does not exist' do
+    result = service.execute("foobar", description)
+    expect(result[:status]).to eq(:error)
+  end
+
+  context 'there already exists a release on a tag' do
+    before do
+      service.execute(tag_name, description)
+    end
+
+    it 'raises an error and does not update the release' do
+      result = service.execute(tag_name, 'The best release!')
+      expect(result[:status]).to eq(:error)
+      expect(project.releases.find_by(tag: tag_name).description).to eq(description)
+    end
+  end
+end
diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb
index 8edabe9450bcf7243bae886dde5a0bffbc8e012d..c800dea04fa14786e748711070d6a86cb6c9903a 100644
--- a/spec/services/create_snippet_service_spec.rb
+++ b/spec/services/create_snippet_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe CreateSnippetService do
+describe CreateSnippetService, services: true do
   before do
     @user = create :user
     @admin = create :user, admin: true
diff --git a/spec/services/destroy_group_service_spec.rb b/spec/services/destroy_group_service_spec.rb
index e28564b386679cb98fb581a9917e3e1f66bf546c..afa89b84175a778dbc10224f686288676aaf8878 100644
--- a/spec/services/destroy_group_service_spec.rb
+++ b/spec/services/destroy_group_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe DestroyGroupService do
+describe DestroyGroupService, services: true do
   let!(:user) { create(:user) }
   let!(:group) { create(:group) }
   let!(:project) { create(:project, namespace: group) }
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index 7756b973ecd8f3c60f0b9ca1e9b069579c17fa52..f6dc9d4008f3e19ace0d6cf1dfa0d677bb390f6f 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe EventCreateService do
+describe EventCreateService, services: true do
   let(:service) { EventCreateService.new }
 
   describe 'Issues' do
diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2bb9c3b3db3f9b66e1a051ceb6fdbe8de8e55c92
--- /dev/null
+++ b/spec/services/git_hooks_service_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe GitHooksService, services: true do
+  include RepoHelpers
+
+  let(:user)    { create :user }
+  let(:project) { create :project }
+  let(:service) { GitHooksService.new }
+
+  before do
+    @blankrev = Gitlab::Git::BLANK_SHA
+    @oldrev = sample_commit.parent_id
+    @newrev = sample_commit.id
+    @ref = 'refs/heads/feature'
+    @repo_path = project.repository.path_to_repo
+  end
+
+  describe '#execute' do
+
+    context 'when receive hooks were successful' do
+      it 'should call post-receive hook' do
+        hook = double(trigger: true)
+        expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
+
+        expect(service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }).to eq(true)
+      end
+    end
+
+    context 'when pre-receive hook failed' do
+      it 'should not call post-receive hook' do
+        expect(service).to receive(:run_hook).with('pre-receive').and_return(false)
+        expect(service).not_to receive(:run_hook).with('post-receive')
+
+        expect do
+          service.execute(user, @repo_path, @blankrev, @newrev, @ref)
+        end.to raise_error(GitHooksService::PreReceiveError)
+      end
+    end
+
+    context 'when update hook failed' do
+      it 'should not call post-receive hook' do
+        expect(service).to receive(:run_hook).with('pre-receive').and_return(true)
+        expect(service).to receive(:run_hook).with('update').and_return(false)
+        expect(service).not_to receive(:run_hook).with('post-receive')
+
+        expect do
+          service.execute(user, @repo_path, @blankrev, @newrev, @ref)
+        end.to raise_error(GitHooksService::PreReceiveError)
+      end
+    end
+
+  end
+end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 17015d29e511c13a7cecc97f7a6b0bce7af62eee..c1080ef190aa2303641d110aa0d45a99b8fa805d 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe GitPushService do
+describe GitPushService, services: true do
   include RepoHelpers
 
   let(:user)          { create :user }
@@ -265,6 +265,75 @@ describe GitPushService do
         expect(Issue.find(issue.id)).to be_opened
       end
     end
+
+    # EE-only tests
+    context "for jira issue tracker" do
+      include JiraServiceHelper
+
+      let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? }
+
+      before do
+        jira_service_settings
+
+        WebMock.stub_request(:post, jira_api_transition_url)
+        WebMock.stub_request(:post, jira_api_comment_url)
+        WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments)
+        WebMock.stub_request(:get, jira_api_test_url)
+
+        allow(closing_commit).to receive_messages({
+                                                    issue_closing_regex: Regexp.new(Gitlab.config.gitlab.issue_closing_pattern),
+                                                    safe_message: message,
+                                                    author_name: commit_author.name,
+                                                    author_email: commit_author.email
+                                                  })
+
+        allow(project.repository).to receive_messages(commits_between: [closing_commit])
+      end
+
+      after do
+        jira_tracker.destroy!
+      end
+
+      context "mentioning an issue" do
+        let(:message) { "this is some work.\n\nrelated to JIRA-1" }
+
+        it "should initiate one api call to jira server to mention the issue" do
+          service.execute(project, user, @oldrev, @newrev, @ref)
+
+          expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
+            body: /mentioned this issue in/
+          ).once
+        end
+      end
+
+      context "closing an issue" do
+        let(:message) { "this is some work.\n\ncloses JIRA-1" }
+
+        it "should initiate one api call to jira server to close the issue" do
+          transition_body = {
+            transition: {
+              id: '2'
+            }
+          }.to_json
+
+          service.execute(project, user, @oldrev, @newrev, @ref)
+          expect(WebMock).to have_requested(:post, jira_api_transition_url).with(
+            body: transition_body
+          ).once
+        end
+
+        it "should initiate one api call to jira server to comment on the issue" do
+          comment_body = {
+            body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]."
+          }.to_json
+
+          service.execute(project, user, @oldrev, @newrev, @ref)
+          expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
+            body: comment_body
+          ).once
+        end
+      end
+    end
   end
 
   describe "empty project" do
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index eed50c7ebac77a8895ed9c00a4017ef521e83456..b982274c529073ee106ed318bf56d49d9c68c9d4 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe GitTagPushService do
+describe GitTagPushService, services: true do
   include RepoHelpers
 
   let(:user) { create :user }
@@ -58,14 +58,14 @@ describe GitTagPushService do
         it { is_expected.to include(timestamp: @commit.date.xmlschema) }
         it do
           is_expected.to include(
-                           url: [
-                             Gitlab.config.gitlab.url,
-                             project.namespace.to_param,
-                             project.to_param,
-                             'commit',
-                             @commit.id
-                           ].join('/')
-                         )
+            url: [
+             Gitlab.config.gitlab.url,
+             project.namespace.to_param,
+             project.to_param,
+             'commit',
+             @commit.id
+            ].join('/')
+          )
         end
 
         context "with a author" do
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
index 4c62fbafd733f147ae1b28739aeab4439e2754c9..6a7ea4b2f44511819617816aab1dca883463f083 100644
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ b/spec/services/issues/bulk_update_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Issues::BulkUpdateService do
+describe Issues::BulkUpdateService, services: true do
   let(:issue) { create(:issue, project: @project) }
 
   before do
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index db547ce0d50a696f1c32b40f88c4da462d1bbd53..3a8daf28f5eb7fed8ef3dc52be033324f1e32cf6 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Issues::CloseService do
+describe Issues::CloseService, services: true do
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
   let(:issue) { create(:issue, assignee: user2) }
@@ -14,7 +14,9 @@ describe Issues::CloseService do
   describe :execute do
     context "valid params" do
       before do
-        @issue = Issues::CloseService.new(project, user, {}).execute(issue)
+        perform_enqueued_jobs do
+          @issue = Issues::CloseService.new(project, user, {}).execute(issue)
+        end
       end
 
       it { expect(@issue).to be_valid }
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 7f1ebcb319897ce69b965524670ddadbc385666d..2148d091a57e35c115e59eefcfd652fdffedeb90 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Issues::CreateService do
+describe Issues::CreateService, services: true do
   let(:project) { create(:empty_project) }
   let(:user) { create(:user) }
 
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index cc3e6483261e13b7f966aafa07d02ad284c4cc3f..87da0e9618b01632dfd86cc3ca5b92cec4984ec2 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Issues::UpdateService do
+describe Issues::UpdateService, services: true do
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
   let(:user3) { create(:user) }
@@ -36,7 +36,10 @@ describe Issues::UpdateService do
           label_ids: [label.id]
         }
 
-        @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+        perform_enqueued_jobs do
+          @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+        end
+
         @issue.reload
       end
 
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index b3cbfd4b5b8d8e9074b186300d10f41191a1a2cd..50d0c2887906d15ab83286aadda5b669004144c9 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe MergeRequests::CloseService do
+describe MergeRequests::CloseService, services: true do
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
   let(:merge_request) { create(:merge_request, assignee: user2) }
@@ -18,7 +18,9 @@ describe MergeRequests::CloseService do
       before do
         allow(service).to receive(:execute_hooks)
 
-        @merge_request = service.execute(merge_request)
+        perform_enqueued_jobs do
+          @merge_request = service.execute(merge_request)
+        end
       end
 
       it { expect(@merge_request).to be_valid }
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index cc64d69361e8a57731a528e0bee56b6c79d89a1a..be8f1676eeb9c4ba1aef4921e0c351a9b29c33c3 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe MergeRequests::CreateService do
+describe MergeRequests::CreateService, services: true do
   let(:project) { create(:project) }
   let(:user) { create(:user) }
 
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 7483f51de034d65d2b8fcfec1d6b02748fbc6b25..ceb3f97280e30ade3079058d90274470d962838f 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe MergeRequests::MergeService do
+describe MergeRequests::MergeService, services: true do
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
   let(:merge_request) { create(:merge_request, assignee: user2) }
@@ -13,12 +13,14 @@ describe MergeRequests::MergeService do
 
   describe :execute do
     context 'valid params' do
-      let(:service) { MergeRequests::MergeService.new(project, user, {}) }
+      let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') }
 
       before do
         allow(service).to receive(:execute_hooks)
 
-        service.execute(merge_request, 'Awesome message')
+        perform_enqueued_jobs do
+          service.execute(merge_request)
+        end
       end
 
       it { expect(merge_request).to be_valid }
@@ -37,14 +39,14 @@ describe MergeRequests::MergeService do
     end
 
     context "error handling" do
-      let(:service) { MergeRequests::MergeService.new(project, user, {}) }
+      let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') }
 
       it 'saves error if there is an exception' do
         allow(service).to receive(:repository).and_raise("error")
 
         allow(service).to receive(:execute_hooks)
 
-        service.execute(merge_request, 'Awesome message')
+        service.execute(merge_request)
 
         expect(merge_request.merge_error).to eq("Something went wrong during merge")
       end
diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..449cecaa7896803fc65055a491f41ff8980d61ce
--- /dev/null
+++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
@@ -0,0 +1,84 @@
+require 'spec_helper'
+
+describe MergeRequests::MergeWhenBuildSucceedsService do
+  let(:user)          { create(:user) }
+  let(:merge_request) { create(:merge_request) }
+
+  let(:mr_merge_if_green_enabled) do
+    create(:merge_request, merge_when_build_succeeds: true, merge_user: user,
+                           source_branch: "source_branch", target_branch: project.default_branch,
+                           source_project: project, target_project: project, state: "opened")
+  end
+
+  let(:project) { create(:project) }
+  let(:ci_commit) { create(:ci_commit_with_one_job, ref: mr_merge_if_green_enabled.source_branch, project: project) }
+  let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, commit_message: 'Awesome message') }
+
+  describe "#execute" do
+    context 'first time enabling' do
+      before do
+        allow(merge_request).to receive(:ci_commit).and_return(ci_commit)
+        service.execute(merge_request)
+      end
+
+      it 'sets the params, merge_user, and flag' do
+        expect(merge_request).to be_valid
+        expect(merge_request.merge_when_build_succeeds).to be_truthy
+        expect(merge_request.merge_params).to eq commit_message: 'Awesome message'
+        expect(merge_request.merge_user).to be user
+      end
+
+      it 'creates a system note' do
+        note = merge_request.notes.last
+        expect(note.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-z]{8}/
+      end
+    end
+
+    context 'already approved' do
+      let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, new_key: true) }
+      let(:build)   { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) }
+
+      before do
+        allow(mr_merge_if_green_enabled).to receive(:ci_commit).and_return(ci_commit)
+        allow(mr_merge_if_green_enabled).to receive(:mergeable?).and_return(true)
+        allow(ci_commit).to receive(:success?).and_return(true)
+      end
+
+      it 'updates the merge params' do
+        expect(SystemNoteService).not_to receive(:merge_when_build_succeeds)
+
+        service.execute(mr_merge_if_green_enabled)
+        expect(mr_merge_if_green_enabled.merge_params).to have_key(:new_key)
+      end
+    end
+  end
+
+  describe "#trigger" do
+    let(:build)     { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") }
+
+    it "merges all merge requests with merge when build succeeds enabled" do
+      allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
+      allow(ci_commit).to receive(:success?).and_return(true)
+
+      expect(MergeWorker).to receive(:perform_async)
+      service.trigger(build)
+    end
+  end
+
+  describe "#cancel" do
+    before do
+      service.cancel(mr_merge_if_green_enabled)
+    end
+
+    it "resets all the merge_when_build_succeeds params" do
+      expect(mr_merge_if_green_enabled.merge_when_build_succeeds).to be_falsey
+      expect(mr_merge_if_green_enabled.merge_params).to eq({})
+      expect(mr_merge_if_green_enabled.merge_user).to be nil
+    end
+
+    it 'Posts a system note' do
+      note = mr_merge_if_green_enabled.notes.last
+      expect(note.note).to include 'Canceled the automatic merge'
+    end
+  end
+end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 7ee4488521d4f39ba331d03295af31c19dda71ce..450250ba032ea19cbfdabf038c4ec48c1c58f58a 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe MergeRequests::RefreshService do
+describe MergeRequests::RefreshService, services: true do
   let(:project) { create(:project) }
   let(:user) { create(:user) }
   let(:service) { MergeRequests::RefreshService }
@@ -17,7 +17,9 @@ describe MergeRequests::RefreshService do
                               source_project: @project,
                               source_branch: 'master',
                               target_branch: 'feature',
-                              target_project: @project)
+                              target_project: @project,
+                              merge_when_build_succeeds: true,
+                              merge_user: @user)
 
       @fork_merge_request = create(:merge_request,
                                    source_project: @fork_project,
@@ -46,6 +48,7 @@ describe MergeRequests::RefreshService do
 
       it { expect(@merge_request.notes).not_to be_empty }
       it { expect(@merge_request).to be_open }
+      it { expect(@merge_request.merge_when_build_succeeds).to be_falsey}
       it { expect(@fork_merge_request).to be_open }
       it { expect(@fork_merge_request.notes).to be_empty }
     end
@@ -146,6 +149,7 @@ describe MergeRequests::RefreshService do
       end
     end
 
+
     def reload_mrs
       @merge_request.reload
       @fork_merge_request.reload
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index 9401bc3b55893ab47e734083b10d2dbd6cead703..ac0221998f55d2d06317b9084548e871fbbbe939 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe MergeRequests::ReopenService do
+describe MergeRequests::ReopenService, services: true do
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
   let(:merge_request) { create(:merge_request, assignee: user2) }
@@ -19,7 +19,9 @@ describe MergeRequests::ReopenService do
         allow(service).to receive(:execute_hooks)
 
         merge_request.state = :closed
-        service.execute(merge_request)
+        perform_enqueued_jobs do
+          service.execute(merge_request)
+        end
       end
 
       it { expect(merge_request).to be_valid }
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 97f5c009aec1f64538e195d43167a0cd1ce229f0..2e9e6e0870da4885b2ebf7b6fb2f5f8ef0340d6b 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe MergeRequests::UpdateService do
+describe MergeRequests::UpdateService, services: true do
   let(:user) { create(:user) }
   let(:user2) { create(:user) }
   let(:user3) { create(:user) }
@@ -42,8 +42,10 @@ describe MergeRequests::UpdateService do
       before do
         allow(service).to receive(:execute_hooks)
 
-        @merge_request = service.execute(merge_request)
-        @merge_request.reload
+        perform_enqueued_jobs do
+          @merge_request = service.execute(merge_request)
+          @merge_request.reload
+        end
       end
 
       it { expect(@merge_request).to be_valid }
diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb
index 034c0f22e12d9c7d7b008587eecfdf36d7972079..1cd6eb2ab382400f77e3b313e4826d7bf3260f5f 100644
--- a/spec/services/milestones/close_service_spec.rb
+++ b/spec/services/milestones/close_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Milestones::CloseService do
+describe Milestones::CloseService, services: true do
   let(:user) { create(:user) }
   let(:project) { create(:project) }
   let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) }
diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb
index 757c9a226d80d1c9057c928427c3f6d385ee6504..c793026e300bfce2b9c6d84d41eebd6c06ac4bd1 100644
--- a/spec/services/milestones/create_service_spec.rb
+++ b/spec/services/milestones/create_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Milestones::CreateService do
+describe Milestones::CreateService, services: true do
   let(:project) { create(:empty_project) }
   let(:user) { create(:user) }
 
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index cc38d2577928e041ff7788df15d3fa9e244560bb..a797a2fe4aaee479a90f9ecaf704f0e44596ed1c 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Notes::CreateService do
+describe Notes::CreateService, services: true do
   let(:project) { create(:empty_project) }
   let(:issue) { create(:issue, project: project) }
   let(:user) { create(:user) }
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 520140917aae2e9f588df8aa5a4a93a1f954c96a..c103752198d7f21c6cb4b1967c9386b5aa6aa906 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -1,8 +1,14 @@
 require 'spec_helper'
 
-describe NotificationService do
+describe NotificationService, services: true do
   let(:notification) { NotificationService.new }
 
+  around(:each) do |example|
+    perform_enqueued_jobs do
+      example.run
+    end
+  end
+
   describe 'Keys' do
     describe :new_key do
       let!(:key) { create(:personal_key) }
@@ -10,8 +16,7 @@ describe NotificationService do
       it { expect(notification.new_key(key)).to be_truthy }
 
       it 'should sent email to key owner' do
-        expect(Notify).to receive(:new_ssh_key_email).with(key.id)
-        notification.new_key(key)
+        expect{ notification.new_key(key) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
       end
     end
   end
@@ -23,8 +28,7 @@ describe NotificationService do
       it { expect(notification.new_email(email)).to be_truthy }
 
       it 'should send email to email owner' do
-        expect(Notify).to receive(:new_email_email).with(email.id)
-        notification.new_email(email)
+        expect{ notification.new_email(email) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
       end
     end
   end
@@ -41,24 +45,28 @@ describe NotificationService do
         project.team << [issue.author, :master]
         project.team << [issue.assignee, :master]
         project.team << [note.author, :master]
+        create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@subscribed_participant cc this guy')
       end
 
       describe :new_note do
         it do
           add_users_with_subscription(note.project, issue)
 
-          should_email(@u_watcher.id)
-          should_email(note.noteable.author_id)
-          should_email(note.noteable.assignee_id)
-          should_email(@u_mentioned.id)
-          should_email(@subscriber.id)
-          should_not_email(note.author_id)
-          should_not_email(@u_participating.id)
-          should_not_email(@u_disabled.id)
-          should_not_email(@unsubscriber.id)
-          should_not_email(@u_outsider_mentioned)
+          ActionMailer::Base.deliveries.clear
 
           notification.new_note(note)
+
+          should_email(@u_watcher)
+          should_email(note.noteable.author)
+          should_email(note.noteable.assignee)
+          should_email(@u_mentioned)
+          should_email(@subscriber)
+          should_email(@subscribed_participant)
+          should_not_email(note.author)
+          should_not_email(@u_participating)
+          should_not_email(@u_disabled)
+          should_not_email(@unsubscriber)
+          should_not_email(@u_outsider_mentioned)
         end
 
         it 'filters out "mentioned in" notes' do
@@ -82,26 +90,20 @@ describe NotificationService do
           group_member = note.project.group.group_members.find_by_user_id(@u_watcher.id)
           group_member.notification_level = Notification::N_GLOBAL
           group_member.save
+          ActionMailer::Base.deliveries.clear
         end
 
         it do
-          should_email(note.noteable.author_id)
-          should_email(note.noteable.assignee_id)
-          should_email(@u_mentioned.id)
-          should_not_email(@u_watcher.id)
-          should_not_email(note.author_id)
-          should_not_email(@u_participating.id)
-          should_not_email(@u_disabled.id)
           notification.new_note(note)
-        end
-      end
 
-      def should_email(user_id)
-        expect(Notify).to receive(:note_issue_email).with(user_id, note.id)
-      end
-
-      def should_not_email(user_id)
-        expect(Notify).not_to receive(:note_issue_email).with(user_id, note.id)
+          should_email(note.noteable.author)
+          should_email(note.noteable.assignee)
+          should_email(@u_mentioned)
+          should_not_email(@u_watcher)
+          should_not_email(note.author)
+          should_not_email(@u_participating)
+          should_not_email(@u_disabled)
+        end
       end
     end
 
@@ -113,24 +115,29 @@ describe NotificationService do
 
       before do
         build_team(note.project)
+        note.project.team << [note.author, :master]
+        ActionMailer::Base.deliveries.clear
       end
 
       describe :new_note do
         it do
+          notification.new_note(note)
+
           # Notify all team members
           note.project.team.members.each do |member|
             # User with disabled notification should not be notified
             next if member.id == @u_disabled.id
-            should_email(member.id)
+            # Author should not be notified
+            next if member.id == note.author.id
+            should_email(member)
           end
-          should_email(note.noteable.author_id)
-          should_email(note.noteable.assignee_id)
 
-          should_not_email(note.author_id)
-          should_not_email(@u_mentioned.id)
-          should_not_email(@u_disabled.id)
-          should_not_email(@u_not_mentioned.id)
-          notification.new_note(note)
+          should_email(note.noteable.author)
+          should_email(note.noteable.assignee)
+          should_not_email(note.author)
+          should_email(@u_mentioned)
+          should_not_email(@u_disabled)
+          should_email(@u_not_mentioned)
         end
 
         it 'filters out "mentioned in" notes' do
@@ -140,14 +147,6 @@ describe NotificationService do
           notification.new_note(mentioned_note)
         end
       end
-
-      def should_email(user_id)
-        expect(Notify).to receive(:note_issue_email).with(user_id, note.id)
-      end
-
-      def should_not_email(user_id)
-        expect(Notify).not_to receive(:note_issue_email).with(user_id, note.id)
-      end
     end
 
     context 'commit note' do
@@ -156,43 +155,38 @@ describe NotificationService do
 
       before do
         build_team(note.project)
+        ActionMailer::Base.deliveries.clear
         allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer)
       end
 
-      describe :new_note do
+      describe :new_note, :perform_enqueued_jobs do
         it do
-          should_email(@u_committer.id, note)
-          should_email(@u_watcher.id, note)
-          should_not_email(@u_mentioned.id, note)
-          should_not_email(note.author_id, note)
-          should_not_email(@u_participating.id, note)
-          should_not_email(@u_disabled.id, note)
           notification.new_note(note)
+
+          should_email(@u_committer)
+          should_email(@u_watcher)
+          should_not_email(@u_mentioned)
+          should_not_email(note.author)
+          should_not_email(@u_participating)
+          should_not_email(@u_disabled)
         end
 
         it do
           note.update_attribute(:note, '@mention referenced')
-          should_email(@u_committer.id, note)
-          should_email(@u_watcher.id, note)
-          should_email(@u_mentioned.id, note)
-          should_not_email(note.author_id, note)
-          should_not_email(@u_participating.id, note)
-          should_not_email(@u_disabled.id, note)
           notification.new_note(note)
+
+          should_email(@u_committer)
+          should_email(@u_watcher)
+          should_email(@u_mentioned)
+          should_not_email(note.author)
+          should_not_email(@u_participating)
+          should_not_email(@u_disabled)
         end
 
         it do
           @u_committer.update_attributes(notification_level: Notification::N_MENTION)
-          should_not_email(@u_committer.id, note)
           notification.new_note(note)
-        end
-
-        def should_email(user_id, n)
-          expect(Notify).to receive(:note_commit_email).with(user_id, n.id)
-        end
-
-        def should_not_email(user_id, n)
-          expect(Notify).not_to receive(:note_commit_email).with(user_id, n.id)
+          should_not_email(@u_committer)
         end
       end
     end
@@ -205,99 +199,69 @@ describe NotificationService do
     before do
       build_team(issue.project)
       add_users_with_subscription(issue.project, issue)
+      ActionMailer::Base.deliveries.clear
     end
 
     describe :new_issue do
       it do
-        should_email(issue.assignee_id)
-        should_email(@u_watcher.id)
-        should_email(@u_participant_mentioned.id)
-        should_not_email(@u_mentioned.id)
-        should_not_email(@u_participating.id)
-        should_not_email(@u_disabled.id)
         notification.new_issue(issue, @u_disabled)
+
+        should_email(issue.assignee)
+        should_email(@u_watcher)
+        should_email(@u_participant_mentioned)
+        should_not_email(@u_mentioned)
+        should_not_email(@u_participating)
+        should_not_email(@u_disabled)
       end
 
       it do
         issue.assignee.update_attributes(notification_level: Notification::N_MENTION)
-        should_not_email(issue.assignee_id)
         notification.new_issue(issue, @u_disabled)
-      end
-
-      def should_email(user_id)
-        expect(Notify).to receive(:new_issue_email).with(user_id, issue.id)
-      end
 
-      def should_not_email(user_id)
-        expect(Notify).not_to receive(:new_issue_email).with(user_id, issue.id)
+        should_not_email(issue.assignee)
       end
     end
 
     describe :reassigned_issue do
       it 'should email new assignee' do
-        should_email(issue.assignee_id)
-        should_email(@u_watcher.id)
-        should_email(@u_participant_mentioned.id)
-        should_email(@subscriber.id)
-        should_not_email(@unsubscriber.id)
-        should_not_email(@u_participating.id)
-        should_not_email(@u_disabled.id)
-
         notification.reassigned_issue(issue, @u_disabled)
-      end
-
-      def should_email(user_id)
-        expect(Notify).to receive(:reassigned_issue_email).with(user_id, issue.id, nil, @u_disabled.id)
-      end
 
-      def should_not_email(user_id)
-        expect(Notify).not_to receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id)
+        should_email(issue.assignee)
+        should_email(@u_watcher)
+        should_email(@u_participant_mentioned)
+        should_email(@subscriber)
+        should_not_email(@unsubscriber)
+        should_not_email(@u_participating)
+        should_not_email(@u_disabled)
       end
     end
 
     describe :close_issue do
       it 'should sent email to issue assignee and issue author' do
-        should_email(issue.assignee_id)
-        should_email(issue.author_id)
-        should_email(@u_watcher.id)
-        should_email(@u_participant_mentioned.id)
-        should_email(@subscriber.id)
-        should_not_email(@unsubscriber.id)
-        should_not_email(@u_participating.id)
-        should_not_email(@u_disabled.id)
-
         notification.close_issue(issue, @u_disabled)
-      end
-
-      def should_email(user_id)
-        expect(Notify).to receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id)
-      end
 
-      def should_not_email(user_id)
-        expect(Notify).not_to receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id)
+        should_email(issue.assignee)
+        should_email(issue.author)
+        should_email(@u_watcher)
+        should_email(@u_participant_mentioned)
+        should_email(@subscriber)
+        should_not_email(@unsubscriber)
+        should_not_email(@u_participating)
+        should_not_email(@u_disabled)
       end
     end
 
     describe :reopen_issue do
       it 'should send email to issue assignee and issue author' do
-        should_email(issue.assignee_id)
-        should_email(issue.author_id)
-        should_email(@u_watcher.id)
-        should_email(@u_participant_mentioned.id)
-        should_email(@subscriber.id)
-        should_not_email(@unsubscriber.id)
-        should_not_email(@u_participating.id)
-        should_not_email(@u_disabled.id)
-
         notification.reopen_issue(issue, @u_disabled)
-      end
-
-      def should_email(user_id)
-        expect(Notify).to receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id)
-      end
 
-      def should_not_email(user_id)
-        expect(Notify).not_to receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id)
+        should_email(issue.assignee)
+        should_email(issue.author)
+        should_email(@u_watcher)
+        should_email(@u_participant_mentioned)
+        should_email(@subscriber)
+        should_not_email(@unsubscriber)
+        should_not_email(@u_participating)
       end
     end
   end
@@ -309,108 +273,74 @@ describe NotificationService do
     before do
       build_team(merge_request.target_project)
       add_users_with_subscription(merge_request.target_project, merge_request)
+      ActionMailer::Base.deliveries.clear
     end
 
     describe :new_merge_request do
       it do
-        should_email(merge_request.assignee_id)
-        should_email(@u_watcher.id)
-        should_email(@u_participant_mentioned.id)
-        should_not_email(@u_participating.id)
-        should_not_email(@u_disabled.id)
         notification.new_merge_request(merge_request, @u_disabled)
-      end
-
-      def should_email(user_id)
-        expect(Notify).to receive(:new_merge_request_email).with(user_id, merge_request.id)
-      end
 
-      def should_not_email(user_id)
-        expect(Notify).not_to receive(:new_merge_request_email).with(user_id, merge_request.id)
+        should_email(merge_request.assignee)
+        should_email(@u_watcher)
+        should_email(@u_participant_mentioned)
+        should_not_email(@u_participating)
+        should_not_email(@u_disabled)
       end
     end
 
     describe :reassigned_merge_request do
       it do
-        should_email(merge_request.assignee_id)
-        should_email(@u_watcher.id)
-        should_email(@u_participant_mentioned.id)
-        should_email(@subscriber.id)
-        should_not_email(@unsubscriber.id)
-        should_not_email(@u_participating.id)
-        should_not_email(@u_disabled.id)
         notification.reassigned_merge_request(merge_request, merge_request.author)
-      end
 
-      def should_email(user_id)
-        expect(Notify).to receive(:reassigned_merge_request_email).with(user_id, merge_request.id, nil, merge_request.author_id)
-      end
-
-      def should_not_email(user_id)
-        expect(Notify).not_to receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id)
+        should_email(merge_request.assignee)
+        should_email(@u_watcher)
+        should_email(@u_participant_mentioned)
+        should_email(@subscriber)
+        should_not_email(@unsubscriber)
+        should_not_email(@u_participating)
+        should_not_email(@u_disabled)
       end
     end
 
     describe :closed_merge_request do
       it do
-        should_email(merge_request.assignee_id)
-        should_email(@u_watcher.id)
-        should_email(@u_participant_mentioned.id)
-        should_email(@subscriber.id)
-        should_not_email(@unsubscriber.id)
-        should_not_email(@u_participating.id)
-        should_not_email(@u_disabled.id)
         notification.close_mr(merge_request, @u_disabled)
-      end
-
-      def should_email(user_id)
-        expect(Notify).to receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
-      end
 
-      def should_not_email(user_id)
-        expect(Notify).not_to receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
+        should_email(merge_request.assignee)
+        should_email(@u_watcher)
+        should_email(@u_participant_mentioned)
+        should_email(@subscriber)
+        should_not_email(@unsubscriber)
+        should_not_email(@u_participating)
+        should_not_email(@u_disabled)
       end
     end
 
     describe :merged_merge_request do
       it do
-        should_email(merge_request.assignee_id)
-        should_email(@u_watcher.id)
-        should_email(@u_participant_mentioned.id)
-        should_email(@subscriber.id)
-        should_not_email(@unsubscriber.id)
-        should_not_email(@u_participating.id)
-        should_not_email(@u_disabled.id)
         notification.merge_mr(merge_request, @u_disabled)
-      end
-
-      def should_email(user_id)
-        expect(Notify).to receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
-      end
 
-      def should_not_email(user_id)
-        expect(Notify).not_to receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
+        should_email(merge_request.assignee)
+        should_email(@u_watcher)
+        should_email(@u_participant_mentioned)
+        should_email(@subscriber)
+        should_not_email(@unsubscriber)
+        should_not_email(@u_participating)
+        should_not_email(@u_disabled)
       end
     end
 
     describe :reopen_merge_request do
       it do
-        should_email(merge_request.assignee_id)
-        should_email(@u_watcher.id)
-        should_email(@u_participant_mentioned.id)
-        should_email(@subscriber.id)
-        should_not_email(@unsubscriber.id)
-        should_not_email(@u_participating.id)
-        should_not_email(@u_disabled.id)
         notification.reopen_mr(merge_request, @u_disabled)
-      end
-
-      def should_email(user_id)
-        expect(Notify).to receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id)
-      end
 
-      def should_not_email(user_id)
-        expect(Notify).not_to receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id)
+        should_email(merge_request.assignee)
+        should_email(@u_watcher)
+        should_email(@u_participant_mentioned)
+        should_email(@subscriber)
+        should_not_email(@unsubscriber)
+        should_not_email(@u_participating)
+        should_not_email(@u_disabled)
       end
     end
   end
@@ -420,22 +350,16 @@ describe NotificationService do
 
     before do
       build_team(project)
+      ActionMailer::Base.deliveries.clear
     end
 
     describe :project_was_moved do
       it do
-        should_email(@u_watcher.id)
-        should_email(@u_participating.id)
-        should_not_email(@u_disabled.id)
         notification.project_was_moved(project, "gitlab/gitlab")
-      end
 
-      def should_email(user_id)
-        expect(Notify).to receive(:project_was_moved_email).with(project.id, user_id, "gitlab/gitlab")
-      end
-
-      def should_not_email(user_id)
-        expect(Notify).not_to receive(:project_was_moved_email).with(project.id, user_id, "gitlab/gitlab")
+        should_email(@u_watcher)
+        should_email(@u_participating)
+        should_not_email(@u_disabled)
       end
     end
   end
@@ -462,11 +386,26 @@ describe NotificationService do
   def add_users_with_subscription(project, issuable)
     @subscriber = create :user
     @unsubscriber = create :user
+    @subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: Notification::N_PARTICIPATING)
 
+    project.team << [@subscribed_participant, :master]
     project.team << [@subscriber, :master]
     project.team << [@unsubscriber, :master]
 
     issuable.subscriptions.create(user: @subscriber, subscribed: true)
+    issuable.subscriptions.create(user: @subscribed_participant, subscribed: true)
     issuable.subscriptions.create(user: @unsubscriber, subscribed: false)
   end
+
+  def sent_to_user?(user)
+    ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
+  end
+
+  def should_email(user)
+    expect(sent_to_user?(user)).to be_truthy
+  end
+
+  def should_not_email(user)
+    expect(sent_to_user?(user)).to be_falsey
+  end
 end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index e81c4edb7d83ea7f08218cea5964b9053f34d39a..5d0b18558b152c81414356dbe52d20e5eab216e4 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Projects::CreateService do
+describe Projects::CreateService, services: true do
   describe :create_by_user do
     before do
       @user = create :user
@@ -49,6 +49,13 @@ describe Projects::CreateService do
       it { expect(@project.namespace).to eq(@group) }
     end
 
+    context 'error handling' do
+      it 'handles invalid options' do
+        @opts.merge!({ default_branch: 'master' } )
+        expect(create_project(@user, @opts)).to eq(nil)
+      end
+    end
+
     context 'wiki_enabled creates repository directory' do
       context 'wiki_enabled true creates wiki repository directory' do
         before do
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index e83eef0b1a28275a1e9f4f9f2eb9de63aaba74b2..1ec2707771730ef4f31b54dfeca9f52a55675942 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Projects::DestroyService do
+describe Projects::DestroyService, services: true do
   let!(:user) { create(:user) }
   let!(:project) { create(:project, namespace: user.namespace) }
   let!(:path) { project.repository.path_to_repo }
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index ddee2e62dfcfed6e9289b4ef6be404566406f74a..5ceed5af9a596ae0d3f2005cb1a3b4d4feae92c2 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Projects::DownloadService do
+describe Projects::DownloadService, services: true do
   describe 'File service' do
     before do
       @user = create :user
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 1feba6ce0489e4cec3a31ed0f73b7a533b05d2bd..d1ee60a0aeac2a390f106aca0922e80c7634e9dc 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Projects::ForkService do
+describe Projects::ForkService, services: true do
   describe :fork_by_user do
     before do
       @from_namespace = create(:namespace)
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 47755bfc990311bdc420d1c0bf94d1fb75fab2d6..c46259431aae3da5878fecb68a896329bb941373 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Projects::TransferService do
+describe Projects::TransferService, services: true do
   let(:user) { create(:user) }
   let(:group) { create(:group) }
   let(:project) { create(:project, namespace: user.namespace) }
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index b347fa15f878ba1226b56a5d44eabf8904e89599..3c06a8901634512a99828a8bfab6144fcfb56a1b 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Projects::UpdateService do
+describe Projects::UpdateService, services: true do
   describe :update_by_user do
     before do
       @user = create :user
@@ -100,6 +100,45 @@ describe Projects::UpdateService do
     end
   end
 
+  describe :visibility_level do
+    let(:user) { create :user, admin: true }
+    let(:project) { create :project, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+    let(:forked_project) { create :forked_project_with_submodules, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+    let(:opts) { {} }
+
+    before do
+      forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, forked_from_project_id: project.id)
+      forked_project.save
+
+      @created_internal = project.internal?
+      @fork_created_internal = forked_project.internal?
+    end
+
+    context 'should update forks visibility level when parent set to more restrictive' do
+      before do
+        opts.merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+        update_project(project, user, opts).inspect
+      end
+
+      it { expect(@created_internal).to be_truthy }
+      it { expect(@fork_created_internal).to be_truthy }
+      it { expect(project.private?).to be_truthy }
+      it { expect(project.forks.first.private?).to be_truthy }
+    end
+
+    context 'should not update forks visibility level when parent set to less restrictive' do
+      before do
+        opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+        update_project(project, user, opts).inspect
+      end
+
+      it { expect(@created_internal).to be_truthy }
+      it { expect(@fork_created_internal).to be_truthy }
+      it { expect(project.public?).to be_truthy }
+      it { expect(project.forks.first.internal?).to be_truthy }
+    end
+  end
+
   def update_project(project, user, opts)
     Projects::UpdateService.new(project, user, opts).execute
   end
diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb
index 1b1a80d1fe731931de7fabe31826750a5fb6b8d3..9268a9fb1a253e4c27394e9e2dbddfd21b8993d3 100644
--- a/spec/services/projects/upload_service_spec.rb
+++ b/spec/services/projects/upload_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe Projects::UploadService do
+describe Projects::UploadService, services: true do
   describe 'File service' do
     before do
       @user = create :user
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index f57bfaea8798f19d0ca7fe145494324b8fd73c3b..7b3a9a75d7c4d535f096a73850d84951f2533c24 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe 'Search::GlobalService' do
+describe 'Search::GlobalService', services: true do
   let(:user) { create(:user) }
   let(:public_user) { create(:user) }
   let(:internal_user) { create(:user) }
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index a31fc1e4b0762ca49f8462e98646d0947f14e787..febc78d2784201e1e5db6528621d641d52456f8e 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe SystemHooksService do
+describe SystemHooksService, services: true do
   let(:user)          { create :user }
   let(:project)       { create :project }
   let(:project_member) { create :project_member }
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index a45130bd473232cf9de5f426d15315b34d17481c..c9f828ae2f7a5e99f63a889e269885b5180e3bef 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe SystemNoteService do
+describe SystemNoteService, services: true do
   let(:project)  { create(:project) }
   let(:author)   { create(:user) }
   let(:noteable) { create(:issue, project: project) }
@@ -207,6 +207,32 @@ describe SystemNoteService do
     end
   end
 
+  describe '.merge_when_build_succeeds' do
+    let(:ci_commit) { build :ci_commit_without_jobs }
+    let(:noteable) { create :merge_request }
+
+    subject { described_class.merge_when_build_succeeds(noteable, project, author, noteable.last_commit) }
+
+    it_behaves_like 'a system note'
+
+    it "posts the Merge When Build Succeeds system note" do
+      expect(subject.note).to match  /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-f]{40} succeeds/
+    end
+  end
+
+  describe '.cancel_merge_when_build_succeeds' do
+    let(:ci_commit) { build :ci_commit_without_jobs }
+    let(:noteable) { create :merge_request }
+
+    subject { described_class.cancel_merge_when_build_succeeds(noteable, project, author) }
+
+    it_behaves_like 'a system note'
+
+    it "posts the Merge When Build Succeeds system note" do
+      expect(subject.note).to eq  "Canceled the automatic merge"
+    end
+  end
+
   describe '.change_title' do
     subject { described_class.change_title(noteable, project, author, 'Old title') }
 
@@ -399,4 +425,65 @@ describe SystemNoteService do
       end
     end
   end
+
+  include JiraServiceHelper
+
+  describe 'JIRA integration' do
+    let(:project)    { create(:project) }
+    let(:author)     { create(:user) }
+    let(:issue)      { create(:issue, project: project) }
+    let(:mergereq)   { create(:merge_request, :simple, target_project: project, source_project: project) }
+    let(:jira_issue) { JiraIssue.new("JIRA-1", project)}
+    let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? }
+    let(:commit)     { project.commit }
+
+    context 'in JIRA issue tracker' do
+      before do
+        jira_service_settings
+        WebMock.stub_request(:post, jira_api_comment_url)
+      end
+
+      after do
+        jira_tracker.destroy!
+      end
+
+      describe "new reference" do
+        before do
+          WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments)
+        end
+
+        subject { described_class.cross_reference(jira_issue, commit, author) }
+
+        it { is_expected.to eq(jira_status_message) }
+      end
+
+      describe "existing reference" do
+        before do
+          message = "[#{author.name}|http://localhost/u/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]."
+          WebMock.stub_request(:get, jira_api_comment_url).to_return(body: "{\"comments\":[{\"body\":\"#{message}\"}]}")
+        end
+
+        subject { described_class.cross_reference(jira_issue, commit, author) }
+        it { is_expected.not_to eq(jira_status_message) }
+      end
+    end
+
+    context 'issue from an issue' do
+      context 'in JIRA issue tracker' do
+        before do
+          jira_service_settings
+          WebMock.stub_request(:post, jira_api_comment_url)
+          WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments)
+        end
+
+        after do
+          jira_tracker.destroy!
+        end
+
+        subject { described_class.cross_reference(jira_issue, issue, author) }
+
+        it { is_expected.to eq(jira_status_message) }
+      end
+    end
+  end
 end
diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb
index 226196eedaead69da50ab676f6c4a2101ba6f351..f034f251ba4dfcd5148457a3fc79aec2ab8d029a 100644
--- a/spec/services/test_hook_service_spec.rb
+++ b/spec/services/test_hook_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe TestHookService do
+describe TestHookService, services: true do
   let(:user)    { create :user }
   let(:project) { create :project }
   let(:hook)    { create :project_hook, project: project }
diff --git a/spec/services/update_release_service_spec.rb b/spec/services/update_release_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bba211089a8889d6a52c842b00bf7f6983485d12
--- /dev/null
+++ b/spec/services/update_release_service_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe UpdateReleaseService, services: true do
+  let(:project) { create(:project) }
+  let(:user) { create(:user) }
+  let(:tag_name) { project.repository.tag_names.first }
+  let(:description) { 'Awesome release!' }
+  let(:new_description) { 'The best release!' }
+  let(:service) { UpdateReleaseService.new(project, user) }
+
+  context 'with an existing release' do
+    let(:create_service) { CreateReleaseService.new(project, user) }
+
+    before do
+      create_service.execute(tag_name, description)
+    end
+
+    it 'successfully updates an existing release' do
+      result = service.execute(tag_name, new_description)
+      expect(result[:status]).to eq(:success)
+      expect(project.releases.find_by(tag: tag_name).description).to eq(new_description)
+    end
+  end
+
+  it 'raises an error if the tag does not exist' do
+    result = service.execute("foobar", description)
+    expect(result[:status]).to eq(:error)
+  end
+
+  it 'raises an error if the release does not exist' do
+    result = service.execute(tag_name, description)
+    expect(result[:status]).to eq(:error)
+  end
+end
diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb
index d7c516e3934e6d2925189373f40e1410aa867e6d..48d114896d0ff5f8a33c21390053e0d43499f697 100644
--- a/spec/services/update_snippet_service_spec.rb
+++ b/spec/services/update_snippet_service_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe UpdateSnippetService do
+describe UpdateSnippetService, services: true do
   before do
     @user = create :user
     @admin = create :user, admin: true
@@ -42,7 +42,7 @@ describe UpdateSnippetService do
     CreateSnippetService.new(project, user, opts).execute
   end
 
-  def update_snippet(project = nil, user, snippet, opts)
+  def update_snippet(project, user, snippet, opts)
     UpdateSnippetService.new(project, user, snippet, opts).execute
   end
 end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 2be13bb3e6a0dd95d1c8962724b12e18517011e0..0225a0ee53f27a87b9ad87b6a3bc4c597a3e494c 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -31,6 +31,7 @@ RSpec.configure do |config|
   config.include StubConfiguration
   config.include RelativeUrl,         type: feature
   config.include TestEnv
+  config.include ActiveJob::TestHelper
   config.include StubGitlabCalls
   config.include StubGitlabData
   config.include BenchmarkMatchers, benchmark: true
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index 97e5c270a59b994222f05f7067702a9665bcec0a..d6e03cbef3dbb940c8e97349c8c9bbb8aac2ba2f 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -1,4 +1,4 @@
-# Helper methods for Gitlab::Markdown filter specs
+# Helper methods for Banzai filter specs
 #
 # Must be included into specs manually
 module FilterSpecHelper
@@ -10,36 +10,49 @@ module FilterSpecHelper
   # if none is provided.
   #
   # html     - HTML String to pass to the filter's `call` method.
-  # contexts - Hash context for the filter. (default: {project: project})
+  # context - Hash context for the filter. (default: {project: project})
   #
   # Returns a Nokogiri::XML::DocumentFragment
-  def filter(html, contexts = {})
+  def filter(html, context = {})
     if defined?(project)
-      contexts.reverse_merge!(project: project)
+      context.reverse_merge!(project: project)
     end
 
-    described_class.call(html, contexts)
+    described_class.call(html, context)
   end
 
   # Run text through HTML::Pipeline with the current filter and return the
   # result Hash
   #
   # body     - String text to run through the pipeline
-  # contexts - Hash context for the filter. (default: {project: project})
+  # context - Hash context for the filter. (default: {project: project})
   #
   # Returns the Hash
-  def pipeline_result(body, contexts = {})
-    contexts.reverse_merge!(project: project) if defined?(project)
+  def pipeline_result(body, context = {})
+    context.reverse_merge!(project: project) if defined?(project)
 
-    pipeline = HTML::Pipeline.new([described_class], contexts)
+    pipeline = HTML::Pipeline.new([described_class], context)
     pipeline.call(body)
   end
 
-  def reference_pipeline_result(body, contexts = {})
-    contexts.reverse_merge!(project: project) if defined?(project)
+  def reference_pipeline(context = {})
+    context.reverse_merge!(project: project) if defined?(project)
 
-    pipeline = HTML::Pipeline.new([described_class, Gitlab::Markdown::ReferenceGathererFilter], contexts)
-    pipeline.call(body)
+    filters = [
+      Banzai::Filter::AutolinkFilter,
+      described_class,
+      Banzai::Filter::ReferenceGathererFilter
+    ]
+
+    HTML::Pipeline.new(filters, context)
+  end
+
+  def reference_pipeline_result(body, context = {})
+    reference_pipeline(context).call(body)
+  end
+
+  def reference_filter(html, context = {})
+    reference_pipeline(context).to_document(html)
   end
 
   # Modify a String reference to make it invalid
diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a3f496359b1675f45aebf946ce24d8b1a7818f87
--- /dev/null
+++ b/spec/support/jira_service_helper.rb
@@ -0,0 +1,67 @@
+module JiraServiceHelper
+
+  def jira_service_settings
+    properties = {
+      "title"=>"JIRA tracker",
+      "project_url"=>"http://jira.example/issues/?jql=project=A",
+      "issues_url"=>"http://jira.example/browse/JIRA-1",
+      "new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa",
+      "api_url"=>"http://jira.example/rest/api/2"
+    }
+
+    jira_tracker.update_attributes(properties: properties, active: true)
+  end
+
+  def jira_status_message
+    "JiraService SUCCESS 200: Successfully posted to #{jira_api_comment_url}."
+  end
+
+  def jira_issue_comments
+    "{\"startAt\":0,\"maxResults\":11,\"total\":11,
+      \"comments\":[{\"self\":\"http://0.0.0.0:4567/rest/api/2/issue/10002/comment/10609\",
+      \"id\":\"10609\",\"author\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",
+      \"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\",
+      \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\",
+      \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\",
+      \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\",
+      \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},
+      \"displayName\":\"GitLab\",\"active\":true},
+      \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned JIRA-1 in Merge request of [gitlab-org/gitlab-test|http://localhost:3000/gitlab-org/gitlab-test/merge_requests/2].\",
+      \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\",
+      \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\",
+      \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\",
+      \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\",
+      \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true},
+      \"created\":\"2015-02-12T22:47:07.826+0100\",
+      \"updated\":\"2015-02-12T22:47:07.826+0100\"},
+     {\"self\":\"http://0.0.0.0:4567/rest/api/2/issue/10002/comment/10700\",
+        \"id\":\"10700\",\"author\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",
+        \"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\",
+        \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\",
+        \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\",
+        \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\",
+        \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true},
+        \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned this issue in [a commit of h5bp/html5-boilerplate|http://localhost:3000/h5bp/html5-boilerplate/commit/2439f77897122fbeee3bfd9bb692d3608848433e].\",
+        \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\",
+        \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\",
+        \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\",
+        \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\",
+        \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true},
+        \"created\":\"2015-04-01T03:45:55.667+0200\",
+        \"updated\":\"2015-04-01T03:45:55.667+0200\"
+      }
+      ]}"
+  end
+
+  def jira_api_comment_url
+    'http://jira.example/rest/api/2/issue/JIRA-1/comment'
+  end
+
+  def jira_api_transition_url
+    'http://jira.example/rest/api/2/issue/JIRA-1/transitions'
+  end
+
+  def jira_api_test_url
+    'http://jira.example/rest/api/2/myself'
+  end
+end
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index bedc1a7f1db47960b8c8c53137782fdf9fbb5a5f..d6d3062a197a84ba7ef8ce33cdecf8c035fdc254 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -93,6 +93,10 @@ class MarkdownFeature
     end
   end
 
+  def urls
+    Gitlab::Application.routes.url_helpers
+  end
+
   def raw_markdown
     markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb'))
     ERB.new(markdown).result(binding)
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 7500d0fdf80bbb0d7ce04d3cc384ba0cb7e1828d..7eadcd58c1fcb061489712c10cc59026a597fe61 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -71,7 +71,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-project_member', count: 3)
+      expect(actual).to have_selector('a.gfm.gfm-project_member', count: 4)
     end
   end
 
@@ -80,7 +80,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-issue', count: 3)
+      expect(actual).to have_selector('a.gfm.gfm-issue', count: 6)
     end
   end
 
@@ -89,7 +89,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 3)
+      expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 6)
       expect(actual).to have_selector('em a.gfm-merge_request')
     end
   end
@@ -99,7 +99,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-snippet', count: 2)
+      expect(actual).to have_selector('a.gfm.gfm-snippet', count: 5)
     end
   end
 
@@ -108,7 +108,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 2)
+      expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 5)
     end
   end
 
@@ -117,7 +117,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-commit', count: 2)
+      expect(actual).to have_selector('a.gfm.gfm-commit', count: 5)
     end
   end
 
@@ -126,7 +126,7 @@ module MarkdownMatchers
     set_default_markdown_messages
 
     match do |actual|
-      expect(actual).to have_selector('a.gfm.gfm-label', count: 3)
+      expect(actual).to have_selector('a.gfm.gfm-label', count: 4)
     end
   end
 
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index 3bb568f4d494858c432f259cf37c20ef7465f535..fce91015fd4b9c2a20d2e053834cffe31473fbad 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -4,18 +4,18 @@
 # - let(:backref_text) { "the way that +subject+ should refer to itself in backreferences " }
 # - let(:set_mentionable_text) { lambda { |txt| "block that assigns txt to the subject's mentionable_text" } }
 
-def common_mentionable_setup
+shared_context 'mentionable context' do
   let(:project) { subject.project }
   let(:author)  { subject.author }
 
   let(:mentioned_issue)  { create(:issue, project: project) }
   let!(:mentioned_mr)     { create(:merge_request, :simple, source_project: project) }
-  let(:mentioned_commit) { project.commit }
+  let(:mentioned_commit) { project.commit("HEAD~1") }
 
   let(:ext_proj)   { create(:project, :public) }
   let(:ext_issue)  { create(:issue, project: ext_proj) }
   let(:ext_mr)     { create(:merge_request, :simple, source_project: ext_proj) }
-  let(:ext_commit) { ext_proj.commit }
+  let(:ext_commit) { ext_proj.commit("HEAD~2") }
 
   # Override to add known commits to the repository stub.
   let(:extra_commits) { [] }
@@ -45,21 +45,18 @@ def common_mentionable_setup
   before do
     # Wire the project's repository to return the mentioned commit, and +nil+
     # for any unrecognized commits.
-    commitmap = {
-      mentioned_commit.id => mentioned_commit
-    }
-    extra_commits.each { |c| commitmap[c.short_id] = c }
-
-    allow(Project).to receive(:find).and_call_original
-    allow(Project).to receive(:find).with(project.id.to_s).and_return(project)
-    allow(project.repository).to receive(:commit) { |sha| commitmap[sha] }
+    allow_any_instance_of(::Repository).to receive(:commit).and_call_original
+    allow_any_instance_of(::Repository).to receive(:commit).with(mentioned_commit.short_id).and_return(mentioned_commit)
+    extra_commits.each do |commit|
+      allow_any_instance_of(::Repository).to receive(:commit).with(commit.short_id).and_return(commit)
+    end
 
     set_mentionable_text.call(ref_string)
   end
 end
 
 shared_examples 'a mentionable' do
-  common_mentionable_setup
+  include_context 'mentionable context'
 
   it 'generates a descriptive back-reference' do
     expect(subject.gfm_reference).to eq(backref_text)
@@ -91,7 +88,7 @@ shared_examples 'a mentionable' do
 end
 
 shared_examples 'an editable mentionable' do
-  common_mentionable_setup
+  include_context 'mentionable context'
 
   it_behaves_like 'a mentionable'
 
diff --git a/spec/support/repo_helpers.rb b/spec/support/repo_helpers.rb
index aadf791bf3ff1ee5c245dc11e31dd1ac1d6f8d25..aa8258d6dad6ef0904684dd8a3869da479010ae4 100644
--- a/spec/support/repo_helpers.rb
+++ b/spec/support/repo_helpers.rb
@@ -45,12 +45,12 @@ eos
 
   def another_sample_commit
     OpenStruct.new(
-        id: "e56497bb5f03a90a51293fc6d516788730953899",
-        parent_id: '4cd80ccab63c82b4bad16faa5193fbd2aa06df40',
-        author_full_name: "Sytse Sijbrandij",
-        author_email: "sytse@gitlab.com",
-        files_changed_count: 1,
-        message: <<eos
+      id: "e56497bb5f03a90a51293fc6d516788730953899",
+      parent_id: '4cd80ccab63c82b4bad16faa5193fbd2aa06df40',
+      author_full_name: "Sytse Sijbrandij",
+      author_email: "sytse@gitlab.com",
+      files_changed_count: 1,
+      message: <<eos
 Add directory structure for tree_helper spec
 
 This directory structure is needed for a testing the method flatten_tree(tree) in the TreeHelper module
diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb
index 5b3eb1bfc5f26f9ea7a1140fc439e716014f1068..eec2e681117bd1dc42627751b3c3a22d98887c72 100644
--- a/spec/support/stub_gitlab_calls.rb
+++ b/spec/support/stub_gitlab_calls.rb
@@ -21,6 +21,10 @@ module StubGitlabCalls
     allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { ci_yaml }
   end
 
+  def stub_ci_builds_disabled
+    allow_any_instance_of(Project).to receive(:builds_enabled?).and_return(false)
+  end
+
   private
 
   def gitlab_url
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 787670e929792e5080975af31f4b5dc50dd96cf0..4f4743bff6d957d5232aa390962752e5996298b6 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -12,6 +12,7 @@ module TestEnv
     'fix'              => '48f0be4',
     'improve/awesome'  => '5937ac0',
     'markdown'         => '0ed8c6c',
+    'lfs'              => 'be93687',
     'master'           => '5937ac0',
     "'test'"           => 'e56497b',
   }
@@ -21,7 +22,8 @@ module TestEnv
   # We currently only need a subset of the branches
   FORKED_BRANCH_SHA = {
     'add-submodule-version-bump' => '3f547c08',
-    'master' => '5937ac0'
+    'master' => '5937ac0',
+    'remove-submodule' => '2a33e0c0'
   }
 
   # Test environment
diff --git a/spec/support/wait_for_ajax.rb b/spec/support/wait_for_ajax.rb
new file mode 100644
index 0000000000000000000000000000000000000000..692d219e9f1ffa16bd7023df43f4305c1d8f8492
--- /dev/null
+++ b/spec/support/wait_for_ajax.rb
@@ -0,0 +1,11 @@
+module WaitForAjax
+  def wait_for_ajax
+    Timeout.timeout(Capybara.default_wait_time) do
+      loop until finished_all_ajax_requests?
+    end
+  end
+
+  def finished_all_ajax_requests?
+    page.evaluate_script('jQuery.active').zero?
+  end
+end
diff --git a/spec/workers/build_email_worker_spec.rb b/spec/workers/build_email_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..98deae0a588366b27eb5f4ce16e87c7a0be66c85
--- /dev/null
+++ b/spec/workers/build_email_worker_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe BuildEmailWorker do
+  include RepoHelpers
+
+  let(:build) { create(:ci_build) }
+  let(:user) { create(:user) }
+  let(:data) { Gitlab::BuildDataBuilder.build(build) }
+
+  subject { BuildEmailWorker.new }
+
+  before do
+    allow(build).to receive(:execute_hooks).and_return(false)
+    build.success
+  end
+
+  describe "#perform" do
+    it "sends mail" do
+      subject.perform(build.id, [user.email], data.stringify_keys)
+
+      email = ActionMailer::Base.deliveries.last
+      expect(email.subject).to include('Build success for')
+      expect(email.to).to eq([user.email])
+    end
+
+    it "gracefully handles an input SMTP error" do
+      ActionMailer::Base.deliveries.clear
+      allow(Notify).to receive(:build_success_email).and_raise(Net::SMTPFatalError)
+
+      subject.perform(build.id, [user.email], data.stringify_keys)
+
+      expect(ActionMailer::Base.deliveries.count).to eq(0)
+    end
+  end
+end
diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb
index 65a8d7d919748131589ec6e5781f4d6502d44041..de40a6f78af61754e0206c7aab04684a450c7cf4 100644
--- a/spec/workers/email_receiver_worker_spec.rb
+++ b/spec/workers/email_receiver_worker_spec.rb
@@ -21,12 +21,14 @@ describe EmailReceiverWorker do
       end
 
       it "sends out a rejection email" do
-        described_class.new.perform(raw_message)
-
-        email = ActionMailer::Base.deliveries.last
-        expect(email).not_to be_nil
-        expect(email.to).to eq(["jake@adventuretime.ooo"])
-        expect(email.subject).to include("Rejected")
+        perform_enqueued_jobs do
+          described_class.new.perform(raw_message)
+
+          email = ActionMailer::Base.deliveries.last
+          expect(email).not_to be_nil
+          expect(email.to).to eq(["jake@adventuretime.ooo"])
+          expect(email.subject).to include("Rejected")
+        end
       end
     end
   end
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 245f066df1f6a016baa738e15e3c1642133e9a6f..dae31992620a0b86de8f8a64352b65cb4c669e0b 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -9,20 +9,22 @@ describe RepositoryForkWorker do
   describe "#perform" do
     it "creates a new repository from a fork" do
       expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).with(
-                                                   project.path_with_namespace,
-                                                   fork_project.namespace.path).
-                                                   and_return(true)
+        project.path_with_namespace,
+        fork_project.namespace.path
+      ).and_return(true)
 
-      subject.perform(project.id,
-                      project.path_with_namespace,
-                      fork_project.namespace.path)
+      subject.perform(
+        project.id,
+        project.path_with_namespace,
+        fork_project.namespace.path)
     end
 
     it "handles bad fork" do
       expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false)
-      subject.perform(project.id,
-                      project.path_with_namespace,
-                      fork_project.namespace.path)
+      subject.perform(
+        project.id,
+        project.path_with_namespace,
+        fork_project.namespace.path)
     end
   end
 end
diff --git a/spec/workers/stuck_ci_builds_worker_spec.rb b/spec/workers/stuck_ci_builds_worker_spec.rb
index f9d87d97014f94e9e791882bbc7dfe6b5a875f66..665ec20f2243a7f5cc68fa72bd244d341bad8ccd 100644
--- a/spec/workers/stuck_ci_builds_worker_spec.rb
+++ b/spec/workers/stuck_ci_builds_worker_spec.rb
@@ -15,7 +15,7 @@ describe StuckCiBuildsWorker do
       end
 
       it 'gets dropped if it was updated over 2 days ago' do
-        build.update!(updated_at: 2.day.ago)
+        build.update!(updated_at: 2.days.ago)
         StuckCiBuildsWorker.new.perform
         is_expected.to eq('failed')
       end
@@ -35,7 +35,7 @@ describe StuckCiBuildsWorker do
       end
 
       it "is still #{status}" do
-        build.update!(updated_at: 2.day.ago)
+        build.update!(updated_at: 2.days.ago)
         StuckCiBuildsWorker.new.perform
         is_expected.to eq(status)
       end