diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ac8390074f4c82a6c40960a892a69e4c871eaf1e..dbdbae9d787d9ed2826b473a78d7fe513f82ec74 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,15 @@
-# This file is generated by GitLab CI
+image: "ruby:2.2"
+
+services:
+  - mysql:latest
+  - postgres:latest
+  - redis:latest
+
+variables:
+  MYSQL_ALLOW_EMPTY_PASSWORD: "1"
+
 before_script:
-  - ./scripts/prepare_build.sh
+  - source ./scripts/prepare_build.sh
   - ruby -v
   - which ruby
   - gem install bundler --no-ri --no-rdoc
@@ -125,3 +134,26 @@ bundler:audit:
     - ruby
     - mysql
   allow_failure: true
+
+# Ruby 2.1 jobs
+
+spec:ruby21:
+  image: ruby:2.1
+  script:
+    - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
+    - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec
+  tags:
+    - ruby
+    - mysql
+  only:
+  - master
+
+spinach:ruby21:
+  image: ruby:2.1
+  script:
+    - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach
+  tags:
+    - ruby
+    - mysql
+  only:
+  - master
diff --git a/.ruby-version b/.ruby-version
index 04b10b4f150135c42748aead459c7ae6caa77c16..530cdd91a205aaea0795fccb77f9314290e441e6 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.1.7
+2.2.4
diff --git a/CHANGELOG b/CHANGELOG
index 2e0eee52a590ee683a6b523c00fae6f4f33e3cd9..58efbe2db7ba203a431a01afcf92b82385e487bf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,9 +1,55 @@
 Please view this file on the master branch, on stable branches it's out of date.
 
 v 8.5.0 (unreleased)
+  - Ensure rake tasks that don't need a DB connection can be run without one
   - Add "visibility" flag to GET /projects api endpoint
-
-v 8.4.0 (unreleased)
+  - Ignore binary files in code search to prevent Error 500 (Stan Hu)
+  - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push
+  - New UI for pagination
+  - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
+    set it up
+  - Fix diff comments loaded by AJAX to load comment with diff in discussion tab
+  - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel)
+  - Don't vendor minified JS
+  - Display 404 error on group not found
+  - Track project import failure
+  - Fix visibility level text in admin area (Zeger-Jan van de Weg)
+  - Update the ExternalIssue regex pattern (Blake Hitchcock)
+  - Revert "Add IP check against DNSBLs at account sign-up"
+  - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
+  - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
+  - Mark inline difference between old and new paths when a file is renamed
+
+v 8.4.3
+  - Increase lfs_objects size column to 8-byte integer to allow files larger
+    than 2.1GB
+  - Correctly highlight MR diff when MR has merge conflicts
+  - Fix highlighting in blame view
+  - Update sentry-raven gem to prevent "Not a git repository" console output
+    when running certain commands
+
+v 8.4.2
+  - Bump required gitlab-workhorse version to bring in a fix for missing
+    artifacts in the build artifacts browser
+  - Get rid of those ugly borders on the file tree view
+  - Fix updating the runner information when asking for builds
+  - Bump gitlab_git version to 7.2.24 in order to bring in a performance
+    improvement when checking if a repository was empty
+  - Add instrumentation for Gitlab::Git::Repository instance methods so we can
+    track them in Performance Monitoring.
+  - Increase contrast between highlighted code comments and inline diff marker
+  - Fix method undefined when using external commit status in builds
+  - Fix highlighting in blame view.
+
+v 8.4.1
+  - Apply security updates for Rails (4.2.5.1), rails-html-sanitizer (1.0.3),
+    and Nokogiri (1.6.7.2)
+  - Fix redirect loop during import
+  - Fix diff highlighting for all syntax themes
+  - Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg)
+  - Delete project and associations in a background worker
+
+v 8.4.0
   - Allow LDAP users to change their email if it was not set by the LDAP server
   - Ensure Gravatar host looks like an actual host
   - Consider re-assign as a mention from a notification point of view
@@ -13,6 +59,8 @@ v 8.4.0 (unreleased)
   - Autocomplete data is now always loaded, instead of when focusing a comment text area
   - Improved performance of finding issues for an entire group
   - Added custom application performance measuring system powered by InfluxDB
+  - Add syntax highlighting to diffs
+  - Gracefully handle invalid UTF-8 sequences in Markdown links (Stan Hu)
   - Bump fog to 1.36.0 (Stan Hu)
   - Add user's last used IP addresses to admin page (Stan Hu)
   - Add housekeeping function to project settings page
@@ -43,7 +91,7 @@ v 8.4.0 (unreleased)
   - Show 'All' tab by default in the builds page
   - Add Open Graph and Twitter Card data to all pages
   - Fix API project lookups when querying with a namespace with dots (Stan Hu)
-  - Enable forcing Two-Factor authentication sitewide, with optional grace period
+  - Enable forcing Two-factor authentication sitewide, with optional grace period
   - Import GitHub Pull Requests into GitLab
   - Change single user API endpoint to return more detailed data (Michael Potthoff)
   - Update version check images to use SVG
@@ -71,6 +119,8 @@ v 8.4.0 (unreleased)
   - Expose button to CI Lint tool on project builds page
   - Fix: Creator should be added as a master of the project on creation
   - Added X-GitLab-... headers to emails from CI and Email On Push services (Anton Baklanov)
+  - Add IP check against DNSBLs at account sign-up
+  - Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
 
 v 8.3.4
   - Use gitlab-workhorse 0.5.4 (fixes API routing bug)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1eabbdc5cad0e0fbcc6eec6af135a4f3b2fec7a0..e7659b06c7124f0e829e1eff96787be1d958e66f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -147,7 +147,7 @@ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true)
 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)
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production)
 
 ## Possible fixes
 
@@ -255,6 +255,8 @@ For examples of feedback on merge requests please look at already
 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.
 
+When having your code reviewed and when reviewing merge requests please take the [thoughtbot code review guidelines](https://github.com/thoughtbot/guides/tree/master/code-review) into account.
+
 ## Definition of done
 
 If you contribute to GitLab please know that changes involve more than just
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index a918a2aa18d5bec6a8bb93891a7a63c243111796..b6160487433ba524a39a93cde5a330f7e71d0d39 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.6.0
+0.6.2
diff --git a/Gemfile b/Gemfile
index 072f7a9fcc8a22959265129731477b592db6c31a..a09d44f8bfdc41d61501dbfcfe1f872832555213 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,6 @@
 source "https://rubygems.org"
 
-gem 'rails', '4.2.4'
+gem 'rails', '4.2.5.1'
 gem 'rails-deprecated_sanitizer', '~> 1.0.3'
 
 # Responders respond_to and respond_with
@@ -49,7 +49,7 @@ gem "browser", '~> 1.0.0'
 
 # Extracting information from a git repository
 # Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 7.2.22'
+gem "gitlab_git", '~> 7.2.23'
 
 # LDAP Auth
 # GitLab fork with several improvements to original library. For full list of changes
@@ -103,7 +103,8 @@ 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'
+# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
+gem 'nokogiri', '1.6.7.2'
 
 # Diffs
 gem 'diffy', '~> 3.0.3'
@@ -212,6 +213,9 @@ gem 'select2-rails',      '~> 3.5.9'
 gem 'virtus',             '~> 1.0.1'
 gem 'net-ssh',            '~> 3.0.1'
 
+# Sentry integration
+gem 'sentry-raven'
+
 # Metrics
 group :metrics do
   gem 'allocations', '~> 1.0', require: false, platform: :mri
@@ -298,7 +302,7 @@ end
 gem "newrelic_rpm", '~> 3.9.4.245'
 gem 'newrelic-grape'
 
-gem 'octokit', '~> 3.7.0'
+gem 'octokit', '~> 3.8.0'
 
 gem "mail_room", "~> 0.6.1"
 
diff --git a/Gemfile.lock b/Gemfile.lock
index a14fdbeed23e7e468ee30d4f50fdb96e75d55fd7..ec92964df25cf4e114f0476eb2dad28e712f68f5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -4,41 +4,41 @@ GEM
     CFPropertyList (2.3.2)
     RedCloth (4.2.9)
     ace-rails-ap (2.0.1)
-    actionmailer (4.2.4)
-      actionpack (= 4.2.4)
-      actionview (= 4.2.4)
-      activejob (= 4.2.4)
+    actionmailer (4.2.5.1)
+      actionpack (= 4.2.5.1)
+      actionview (= 4.2.5.1)
+      activejob (= 4.2.5.1)
       mail (~> 2.5, >= 2.5.4)
       rails-dom-testing (~> 1.0, >= 1.0.5)
-    actionpack (4.2.4)
-      actionview (= 4.2.4)
-      activesupport (= 4.2.4)
+    actionpack (4.2.5.1)
+      actionview (= 4.2.5.1)
+      activesupport (= 4.2.5.1)
       rack (~> 1.6)
       rack-test (~> 0.6.2)
       rails-dom-testing (~> 1.0, >= 1.0.5)
       rails-html-sanitizer (~> 1.0, >= 1.0.2)
-    actionview (4.2.4)
-      activesupport (= 4.2.4)
+    actionview (4.2.5.1)
+      activesupport (= 4.2.5.1)
       builder (~> 3.1)
       erubis (~> 2.7.0)
       rails-dom-testing (~> 1.0, >= 1.0.5)
       rails-html-sanitizer (~> 1.0, >= 1.0.2)
-    activejob (4.2.4)
-      activesupport (= 4.2.4)
+    activejob (4.2.5.1)
+      activesupport (= 4.2.5.1)
       globalid (>= 0.3.0)
-    activemodel (4.2.4)
-      activesupport (= 4.2.4)
+    activemodel (4.2.5.1)
+      activesupport (= 4.2.5.1)
       builder (~> 3.1)
-    activerecord (4.2.4)
-      activemodel (= 4.2.4)
-      activesupport (= 4.2.4)
+    activerecord (4.2.5.1)
+      activemodel (= 4.2.5.1)
+      activesupport (= 4.2.5.1)
       arel (~> 6.0)
     activerecord-deprecated_finders (1.0.4)
     activerecord-session_store (0.1.2)
       actionpack (>= 4.0.0, < 5)
       activerecord (>= 4.0.0, < 5)
       railties (>= 4.0.0, < 5)
-    activesupport (4.2.4)
+    activesupport (4.2.5.1)
       i18n (~> 0.7)
       json (~> 1.7, >= 1.7.7)
       minitest (~> 5.1)
@@ -356,7 +356,7 @@ GEM
       posix-spawn (~> 0.3)
     gitlab_emoji (0.2.0)
       gemojione (~> 2.1)
-    gitlab_git (7.2.22)
+    gitlab_git (7.2.24)
       activesupport (~> 4.0)
       charlock_holmes (~> 0.7.3)
       github-linguist (~> 4.7.0)
@@ -482,7 +482,7 @@ GEM
       grape
       newrelic_rpm
     newrelic_rpm (3.9.4.245)
-    nokogiri (1.6.7.1)
+    nokogiri (1.6.7.2)
       mini_portile2 (~> 2.0.0.rc2)
     nprogress-rails (0.1.6.7)
     oauth (0.4.7)
@@ -492,7 +492,7 @@ GEM
       multi_json (~> 1.3)
       multi_xml (~> 0.5)
       rack (~> 1.2)
-    octokit (3.7.1)
+    octokit (3.8.0)
       sawyer (~> 0.6.0, >= 0.5.3)
     omniauth (1.2.2)
       hashie (>= 1.2, < 4)
@@ -588,16 +588,16 @@ GEM
       rack
     rack-test (0.6.3)
       rack (>= 1.0)
-    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)
+    rails (4.2.5.1)
+      actionmailer (= 4.2.5.1)
+      actionpack (= 4.2.5.1)
+      actionview (= 4.2.5.1)
+      activejob (= 4.2.5.1)
+      activemodel (= 4.2.5.1)
+      activerecord (= 4.2.5.1)
+      activesupport (= 4.2.5.1)
       bundler (>= 1.3.0, < 2.0)
-      railties (= 4.2.4)
+      railties (= 4.2.5.1)
       sprockets-rails
     rails-deprecated_sanitizer (1.0.3)
       activesupport (>= 4.2.0.alpha)
@@ -605,11 +605,11 @@ GEM
       activesupport (>= 4.2.0.beta, < 5.0)
       nokogiri (~> 1.6.0)
       rails-deprecated_sanitizer (>= 1.0.1)
-    rails-html-sanitizer (1.0.2)
+    rails-html-sanitizer (1.0.3)
       loofah (~> 2.0)
-    railties (4.2.4)
-      actionpack (= 4.2.4)
-      activesupport (= 4.2.4)
+    railties (4.2.5.1)
+      actionpack (= 4.2.5.1)
+      activesupport (= 4.2.5.1)
       rake (>= 0.8.7)
       thor (>= 0.18.1, < 2.0)
     rainbow (2.0.0)
@@ -725,6 +725,8 @@ GEM
       activesupport (>= 3.1, < 4.3)
     select2-rails (3.5.9.3)
       thor (~> 0.14)
+    sentry-raven (0.15.4)
+      faraday (>= 0.7.6)
     settingslogic (2.0.9)
     sexp_processor (4.6.0)
     sham_rack (1.3.6)
@@ -932,7 +934,7 @@ DEPENDENCIES
   github-markup (~> 1.3.1)
   gitlab-flowdock-git-hook (~> 1.0.1)
   gitlab_emoji (~> 0.2.0)
-  gitlab_git (~> 7.2.22)
+  gitlab_git (~> 7.2.23)
   gitlab_meta (= 7.0)
   gitlab_omniauth-ldap (~> 1.2.1)
   gollum-lib (~> 4.1.0)
@@ -960,10 +962,10 @@ DEPENDENCIES
   net-ssh (~> 3.0.1)
   newrelic-grape
   newrelic_rpm (~> 3.9.4.245)
-  nokogiri (= 1.6.7.1)
+  nokogiri (= 1.6.7.2)
   nprogress-rails (~> 0.1.6.7)
   oauth2 (~> 1.0.0)
-  octokit (~> 3.7.0)
+  octokit (~> 3.8.0)
   omniauth (~> 1.2.2)
   omniauth-azure-oauth2 (~> 0.0.6)
   omniauth-bitbucket (~> 0.0.2)
@@ -986,7 +988,7 @@ DEPENDENCIES
   rack-attack (~> 4.3.1)
   rack-cors (~> 0.4.0)
   rack-oauth2 (~> 1.2.1)
-  rails (= 4.2.4)
+  rails (= 4.2.5.1)
   rails-deprecated_sanitizer (~> 1.0.3)
   raphael-rails (~> 2.1.2)
   rblineprof
@@ -1008,6 +1010,7 @@ DEPENDENCIES
   sdoc (~> 0.3.20)
   seed-fu (~> 2.3.5)
   select2-rails (~> 3.5.9)
+  sentry-raven
   settingslogic (~> 2.0.9)
   sham_rack
   shoulda-matchers (~> 2.8.0)
diff --git a/README.md b/README.md
index 3ec1d4a776cb68afff4a253fcd1d0f5b08c7964b..22dbf841bdc4b66102cfb4212ac1ccb65d2b67d8 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
 GitLab is a Ruby on Rails application that runs on the following software:
 
 - Ubuntu/Debian/CentOS/RHEL
-- Ruby (MRI) 2.1
+- Ruby (MRI) 2.1 or 2.2
 - Git 1.7.10+
 - Redis 2.8+
 - MySQL or PostgreSQL
diff --git a/Rakefile b/Rakefile
old mode 100644
new mode 100755
diff --git a/app/assets/fonts/OFL.txt b/app/assets/fonts/OFL.txt
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-Black.ttf.woff b/app/assets/fonts/SourceSansPro-Black.ttf.woff
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-Black.ttf.woff2 b/app/assets/fonts/SourceSansPro-Black.ttf.woff2
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff b/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf.woff b/app/assets/fonts/SourceSansPro-Bold.ttf.woff
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf.woff2 b/app/assets/fonts/SourceSansPro-Bold.ttf.woff2
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff b/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff b/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2 b/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff b/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-It.ttf.woff b/app/assets/fonts/SourceSansPro-It.ttf.woff
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-It.ttf.woff2 b/app/assets/fonts/SourceSansPro-It.ttf.woff2
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-Light.ttf.woff b/app/assets/fonts/SourceSansPro-Light.ttf.woff
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-Light.ttf.woff2 b/app/assets/fonts/SourceSansPro-Light.ttf.woff2
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff b/app/assets/fonts/SourceSansPro-LightIt.ttf.woff
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf.woff b/app/assets/fonts/SourceSansPro-Regular.ttf.woff
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf.woff2 b/app/assets/fonts/SourceSansPro-Regular.ttf.woff2
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff b/app/assets/fonts/SourceSansPro-Semibold.ttf.woff
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2 b/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff b/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff
old mode 100755
new mode 100644
diff --git a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2
old mode 100755
new mode 100644
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index c095e5ae2b15dcb5bd581ae66458fe781dfaf5fc..d5e6ff0717a6d1c1fcc6b0a6cce100d95cb57211 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -5,7 +5,10 @@
 # the compiled file.
 #
 #= require jquery
-#= require jquery-ui
+#= require jquery-ui/autocomplete
+#= require jquery-ui/datepicker
+#= require jquery-ui/effect-highlight
+#= require jquery-ui/sortable
 #= require jquery_ujs
 #= require jquery.cookie
 #= require jquery.endless-scroll
@@ -21,9 +24,9 @@
 #= require bootstrap
 #= require select2
 #= require raphael
-#= require g.raphael-min
-#= require g.bar-min
-#= require chart-lib.min
+#= require g.raphael
+#= require g.bar
+#= require Chart
 #= require branch-graph
 #= require ace/ace
 #= require ace/ext-searchbox
@@ -38,9 +41,9 @@
 #= require shortcuts_dashboard_navigation
 #= require shortcuts_issuable
 #= require shortcuts_network
-#= require jquery.nicescroll.min
+#= require jquery.nicescroll
 #= require_tree .
-#= require fuzzaldrin-plus.min
+#= require fuzzaldrin-plus
 
 window.slugify = (text) ->
   text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
@@ -203,4 +206,13 @@ $ ->
     form = btn.closest("form")
     new ConfirmDangerModal(form, text)
 
+  $('input[type="search"]').each ->
+    $this = $(this)
+    $this.attr 'value', $this.val()
+    return
+    
+  $(document).on 'keyup', 'input[type="search"]' , (e) ->
+    $this = $(this)
+    $this.attr 'value', $this.val()
+
   new Aside()
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 9d5ae6c04e9d9425c684a167acb51302ddbf018d..047df4786a95641476fd4235268ffefb3ed630e9 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -4,6 +4,7 @@ class @AwardsHandler
       event.stopPropagation()
       event.preventDefault()
       $(".emoji-menu").show()
+      $("#emoji_search").focus()
 
     $("html").on 'click', (event) ->
       if !$(event.target).closest(".emoji-menu").length
@@ -44,7 +45,6 @@ class @AwardsHandler
   decrementCounter: (emoji) ->
     counter = @findEmojiIcon(emoji).siblings(".counter")
     emojiIcon = counter.parent()
-
     if parseInt(counter.text()) > 1
       counter.text(parseInt(counter.text()) - 1)
       emojiIcon.removeClass("active")
@@ -60,20 +60,18 @@ class @AwardsHandler
   removeMeFromAuthorList: (emoji) ->
     award_block = @findEmojiIcon(emoji).parent()
     authors = award_block.attr("data-original-title").split(", ")
-    authors = _.without(authors, "me").join(", ")
-    award_block.attr("title", authors)
+    authors.splice(authors.indexOf("me"),1)
+    award_block.closest(".award").attr("data-original-title", authors.join(", "))
     @resetTooltip(award_block)
 
   addMeToAuthorList: (emoji) ->
     award_block = @findEmojiIcon(emoji).parent()
-    authors = _.compact(award_block.attr("data-original-title").split(", "))
+    origTitle = award_block.attr("data-original-title").trim()
+    authors = []
+    if origTitle
+      authors = origTitle.split(', ')
     authors.push("me")
-
-    if authors.length == 1
-      award_block.attr("title", "me")
-    else
-      award_block.attr("title", authors.join(", "))
-
+    award_block.attr("title", authors.join(", "))
     @resetTooltip(award_block)
 
   resetTooltip: (award) ->
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
index f6bf836f19f3e4e594c4c077581ac6439371bde4..390e41ed8d4ca56ee8bfb9b2ad16e724e2cbe7e9 100644
--- a/app/assets/javascripts/blob/edit_blob.js.coffee
+++ b/app/assets/javascripts/blob/edit_blob.js.coffee
@@ -32,6 +32,7 @@ class @EditBlob
           content: editor.getValue()
         , (response) ->
           currentPane.empty().append response
+          currentPane.syntaxHighlight()
           return
 
       else
diff --git a/app/assets/javascripts/build_artifacts.js.coffee b/app/assets/javascripts/build_artifacts.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..5ae6cba56c8ee020e69da29806799b3b25569482
--- /dev/null
+++ b/app/assets/javascripts/build_artifacts.js.coffee
@@ -0,0 +1,14 @@
+class @BuildArtifacts
+  constructor: () ->
+    @disablePropagation()
+    @setupEntryClick()
+
+  disablePropagation: ->
+    $('.top-block').on 'click', '.download',  (e) ->
+      e.stopPropagation()
+    $('.tree-holder').on 'click', 'tr[data-link] a', (e) ->
+      e.stopImmediatePropagation()
+
+  setupEntryClick: ->
+    $('.tree-holder').on 'click', 'tr[data-link]', (e) ->
+      window.location = @dataset.link
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 58d6b9d406035154950868e9e73621cfe4b68b56..2cdf01d874cda3e2632d6ff0372dcf3d45a67947 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -87,7 +87,6 @@ class Dispatcher
         new GroupAvatar()
       when 'projects:tree:show'
         new TreeView()
-        shortcut_handler = new ShortcutsTree()
       when 'projects:find_file:show'
         shortcut_handler = true
       when 'projects:blob:show'
@@ -101,6 +100,8 @@ class Dispatcher
         shortcut_handler = true
       when 'projects:forks:new'
         new ProjectFork()
+      when 'projects:artifacts:browse'
+        new BuildArtifacts()
       when 'users:show'
         new User()
         new Activities()
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index cbc70cd846cfb50f2128f2fd78d5af9d57bb3531..d663e34871c244bccefb4b01bfdab00b12a206d6 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -50,6 +50,7 @@ class @Issue
           new Flash(issueFailMessage, 'alert')
         success: (data, textStatus, jqXHR) ->
           if data.saved
+            $(document).trigger('issuable:change');
             if isClose
               $('a.btn-close').addClass('hidden')
               $('a.btn-reopen').removeClass('hidden')
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 2bfc5cb2d9c556de76b431060377b3e810013d86..3347ab65c9022c59a76459f1ea83a806ccd6967b 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -15,6 +15,8 @@ class @Notes
     @last_fetched_at = last_fetched_at
     @view = view
     @noteable_url = document.URL
+    @notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")
+
     @initRefresh()
     @setupMainTargetNoteForm()
     @cleanBinding()
@@ -62,6 +64,9 @@ class @Notes
     # fetch notes when tab becomes visible
     $(document).on "visibilitychange", @visibilityChange
 
+    # when issue status changes, we need to refresh data
+    $(document).on "issuable:change", @refresh
+
   cleanBinding: ->
     $(document).off "ajax:success", ".js-main-target-form"
     $(document).off "ajax:success", ".js-discussion-note-form"
@@ -89,7 +94,7 @@ class @Notes
     , 15000
 
   refresh: ->
-    unless document.hidden or (@noteable_url != document.URL)
+    if not document.hidden and document.URL.indexOf(@noteable_url) is 0
       @getContent()
 
   getContent: ->
@@ -101,7 +106,10 @@ class @Notes
         notes = data.notes
         @last_fetched_at = data.last_fetched_at
         $.each notes, (i, note) =>
-          @renderNote(note)
+          if note.discussion_with_diff_html?
+            @renderDiscussionNote(note)
+          else
+            @renderNote(note)
 
 
   ###
@@ -116,18 +124,21 @@ class @Notes
         flash.pinTo('.header-content')
       return
 
+    if note.award
+      awards_handler.addAwardToEmojiBar(note.note)
+      awards_handler.scrollToAwards()
+
     # render note if it not present in loaded list
     # or skip if rendered
-    if @isNewNote(note) && !note.award
+    else if @isNewNote(note)
       @note_ids.push(note.id)
-      $('ul.main-notes-list').
-        append(note.html).
-        syntaxHighlight()
+
+      $('ul.main-notes-list')
+        .append(note.html)
+        .syntaxHighlight()
       @initTaskList()
+      @updateNotesCount(1)
 
-    if note.award
-      awards_handler.addAwardToEmojiBar(note.note)
-      awards_handler.scrollToAwards()
 
   ###
   Check if note does not exists on page
@@ -144,34 +155,39 @@ class @Notes
   Note: for rendering inline notes use renderDiscussionNote
   ###
   renderDiscussionNote: (note) ->
+    return unless @isNewNote(note)
+
     @note_ids.push(note.id)
-    form = $("form[rel='" + note.discussion_id + "']")
+    form = $("#new-discussion-note-form-#{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")
+    discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
+    if discussionContainer.length is 0
       # insert the note and the reply button after the temp row
       row.after note.discussion_html
 
       # remove the note (will be added again below)
       row.next().find(".note").remove()
 
+      # Before that, the container didn't exist
+      discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
+
       # Add note to 'Changes' page discussions
-      $(".notes[rel='" + note.discussion_id + "']").append note_html
+      discussionContainer.append note_html
 
       # Init discussion on 'Discussion' page if it is merge request page
-      if $('body').attr('data-page').indexOf('projects:merge_request') == 0
-        discussion_html = $(note.discussion_with_diff_html)
-        discussion_html.syntaxHighlight()
-        $('ul.main-notes-list').append(discussion_html)
+      if $('body').attr('data-page').indexOf('projects:merge_request') is 0
+        $('ul.main-notes-list')
+          .append(note.discussion_with_diff_html)
+          .syntaxHighlight()
     else
       # append new note to all matching discussions
-      $(".notes[rel='" + note.discussion_id + "']").append note_html
+      discussionContainer.append note_html
 
-    # cleanup after successfully creating a diff/discussion note
-    @removeDiscussionNoteForm(form)
+    @updateNotesCount(1)
 
   ###
   Called in response the main target form has been successfully submitted.
@@ -278,6 +294,9 @@ class @Notes
   addDiscussionNote: (xhr, note, status) =>
     @renderDiscussionNote(note)
 
+    # cleanup after successfully creating a diff/discussion note
+    @removeDiscussionNoteForm($("#new-discussion-note-form-#{note.discussion_id}"))
+
   ###
   Called in response to the edit note form being submitted
 
@@ -349,30 +368,32 @@ class @Notes
   Removes the actual note from view.
   Removes the whole discussion if the last note is being removed.
   ###
-  removeNote: ->
-    note = $(this).closest(".note")
-    note_id = note.attr('id')
+  removeNote: (e) =>
+    noteId = $(e.currentTarget)
+               .closest(".note")
+               .attr("id")
 
-    $('.note[id="' + note_id + '"]').each ->
-      note = $(this)
+    # A same note appears in the "Discussion" and in the "Changes" tab, we have
+    # to remove all. Using $(".note[id='noteId']") ensure we get all the notes,
+    # where $("#noteId") would return only one.
+    $(".note[id='#{noteId}']").each (i, el) =>
+      note  = $(el)
       notes = note.closest(".notes")
-      count = notes.closest(".issuable-details").find(".notes-tab .badge")
 
       # check if this is the last note for this line
       if notes.find(".note").length is 1
 
-        # for discussions
-        notes.closest(".discussion").remove()
+        # "Discussions" tab
+        notes.closest(".timeline-entry").remove()
 
-        # for diff lines
+        # "Changes" tab / commit view
         notes.closest("tr").remove()
 
-      # update notes count
-      oldNum = parseInt(count.text())
-      count.text(oldNum - 1)
-
       note.remove()
 
+    # Decrement the "Discussions" counter only once
+    @updateNotesCount(-1)
+
   ###
   Called in response to clicking the delete attachment link
 
@@ -412,7 +433,7 @@ class @Notes
   ###
   setupDiscussionNoteForm: (dataHolder, form) =>
     # setup note target
-    form.attr "rel", dataHolder.data("discussionId")
+    form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}"
     form.find("#line_type").val dataHolder.data("lineType")
     form.find("#note_commit_id").val dataHolder.data("commitId")
     form.find("#note_line_code").val dataHolder.data("lineCode")
@@ -542,3 +563,6 @@ class @Notes
 
   updateTaskList: ->
     $('form', this).submit()
+
+  updateNotesCount: (updateCount) ->
+    @notesCountBadge.text(parseInt(@notesCountBadge.text()) + updateCount)
diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee
index f2887af190bc5a9e4093786822c0e51138a881e1..b71509dbc5a62cbfc2942dfab6d3eb46a039c525 100644
--- a/app/assets/javascripts/projects_list.js.coffee
+++ b/app/assets/javascripts/projects_list.js.coffee
@@ -9,11 +9,13 @@ class @ProjectsList
     $(".projects-list-filter").keyup ->
       terms = $(this).val()
       uiBox = $('div.projects-list-holder')
+      filterSelector = $(this).data('filter-selector') || 'span.filter-title'
+
       if terms == "" || terms == undefined
         uiBox.find("ul.projects-list li").show()
       else
         uiBox.find("ul.projects-list li").each (index) ->
-          name = $(this).find("span.filter-title").text()
+          name = $(this).find(filterSelector).text()
 
           if name.toLowerCase().search(terms.toLowerCase()) == -1
             $(this).hide()
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
index 4d915bfc8c5c3e83b8d2bfd94659050ed2d0edcd..f141fb69c3ee474dbb13b7c15e1f56d8a9c1e51f 100644
--- a/app/assets/javascripts/shortcuts.js.coffee
+++ b/app/assets/javascripts/shortcuts.js.coffee
@@ -4,6 +4,7 @@ class @Shortcuts
     Mousetrap.reset()
     Mousetrap.bind('?', @selectiveHelp)
     Mousetrap.bind('s', Shortcuts.focusSearch)
+    Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL?
 
   selectiveHelp: (e) =>
     Shortcuts.showHelp(e, @enabledHelp)
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
index bb5321946826a584936331fb78afc84dd043ef7e..f717a753cf80ea1cee75677bc6078842223df53f 100644
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -5,11 +5,11 @@ class @ShortcutsIssuable extends ShortcutsNavigation
   constructor: (isMergeRequest) ->
     super()
     Mousetrap.bind('a', ->
-      $('.js-assignee').select2('open')
+      $('.block.assignee .edit-link').trigger('click')
       return false
     )
     Mousetrap.bind('m', ->
-      $('.js-milestone').select2('open')
+      $('.block.milestone .edit-link').trigger('click')
       return false
     )
     Mousetrap.bind('r', =>
diff --git a/app/assets/javascripts/shortcuts_tree.coffee b/app/assets/javascripts/shortcuts_tree.coffee
deleted file mode 100644
index ba0839c9fc005ea96cc68acf9ff7069ead5d425b..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/shortcuts_tree.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-class @ShortcutsTree extends ShortcutsNavigation
-  constructor: ->
-    super()
-    Mousetrap.bind('t', -> ShortcutsTree.findAndFollowLink('.shortcuts-find-file'))
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index d0f5d33bf4d0a6f344c6efda9993cba5a1d94e80..bd89cc7dc1d2bae4aca2c1494ab8b110c66cc54d 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -146,6 +146,10 @@
   border-bottom: 1px solid $border-color;
 
   &.oneline-block {
-    line-height: 42px;
+    line-height: 36px;
+  }
+
+  > .controls {
+    float: right;
   }
 }
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 585a9d8391336afeb14b3d0e7f31444b88b2effe..6ea2219073c6e102f94924dd3c001bf93acd7710 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -120,14 +120,6 @@ span.update-author {
   display: inline;
 }
 
-.line_holder {
-  &:hover {
-    td {
-      background: #FFFFCF !important;
-    }
-  }
-}
-
 p.time {
   color: #999;
   font-size: 90%;
@@ -319,14 +311,6 @@ table {
   }
 }
 
-.wiki .highlight, .note-body .highlight {
-  margin: 12px 0 12px 0;
-}
-
-.wiki .code {
-  overflow-x: auto;
-}
-
 .footer-links {
   margin-bottom: 20px;
   a {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 6ee104ee31a575731680d569c7fcd38d22d6a5be..c7f3604850df956739cbed5bbc1de8b17c42480b 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -7,6 +7,7 @@
   border: 1px solid $border-color;
 
   &.readme-holder {
+    margin-top: 10px;
     border-bottom: 0;
   }
 
@@ -35,6 +36,20 @@
       }
     }
 
+    .filename {
+      &.old {
+        span.idiff {
+          background-color: #f8cbcb;
+        }
+      }
+
+      &.new {
+        span.idiff {
+          background-color: #a6f3a6;
+        }
+      }
+    }
+
     .left-options {
       margin-top: -3px;
     }
@@ -92,15 +107,6 @@
         &:last-child {
           border-right: none;
         }
-        background: #fff;
-      }
-      .lines {
-        pre {
-          padding: 0;
-          margin: 0;
-          background: none;
-          border: none;
-        }
       }
       img.avatar {
         border: 0 none;
@@ -116,18 +122,18 @@
           color: #888;
         }
       }
-      td.blame-numbers {
-        pre {
-          color: #AAA;
-          white-space: pre;
-        }
-        background: #f1f1f1;
+      td.line-numbers {
+        float: none;
         border-left: 1px solid #DDD;
       }
       td.lines {
+        padding: 0;
         code {
           font-family: $monospace_font;
         }
+        pre {
+          margin: 0;
+        }
       }
     }
 
@@ -166,3 +172,15 @@
     }
   }
 }
+
+span.idiff {
+  &.left {
+    border-top-left-radius: 2px;
+    border-bottom-left-radius: 2px;
+  }
+
+  &.right {
+    border-top-right-radius: 2px;
+    border-bottom-right-radius: 2px;
+  }
+}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 4dab806d50e4338dd581d843df6f0255c38add79..d097e4d32f7f308aef85ef0fc9eca0de514f1a4b 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -2,11 +2,42 @@ textarea {
   resize: vertical;
 }
 
-input[type='search'].search-text-input {
-  background-image: image-url("icon-search.png");
+input {
+  border-radius: $border-radius-base;
+}
+
+input[type='search'] {
+  background-color: white;
+  padding-left: 10px;
+}
+
+input[type='search'].search-input {
   background-repeat: no-repeat;
   background-position: 10px;
-  padding-left: 25px;
+  background-size: 16px;
+  background-position-x: 30%;
+  padding-left: 10px;
+  background-color: $gray-light;
+
+  &.search-input[value=""] {
+    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC');
+  }
+
+  &.search-input::-webkit-input-placeholder {
+    text-align: center;
+  }
+
+  &.search-input:-moz-placeholder { /* Firefox 18- */
+    text-align: center;  
+  }
+
+  &.search-input::-moz-placeholder {  /* Firefox 19+ */
+    text-align: center;  
+  }
+
+  &.search-input:-ms-input-placeholder {  
+    text-align: center; 
+  }
 }
 
 input[type='text'].danger {
@@ -74,6 +105,7 @@ label {
 
 .form-control {
   @include box-shadow(none);
+  border-radius: 3px;
 }
 
 .form-control-inline {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index ba5e72c8c5a45357ced756972600ad90764535fc..f875b1460e76664ce2433327746b91c27ec45092 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -108,16 +108,10 @@ header {
 
     .search-input {
       width: 220px;
-      background-image: image-url("icon-search.png");
-      background-repeat: no-repeat;
-      background-position: 195px;
-      @include input-big;
 
       &:focus {
         @include box-shadow(none);
         outline: none;
-        border-color: #DDD;
-        background-color: #FFF;
       }
     }
   }
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index 2e13ee842e0178c72cf0508aa106ca658906dc84..9854df4c45c5b3d820b17dbb4a88c282d4694856 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -17,6 +17,7 @@
     overflow-y: hidden;
     white-space: pre;
     word-wrap: normal;
+    border-left: 1px solid;
 
     code {
       font-family: $monospace_font;
@@ -25,7 +26,7 @@
       padding: 0;
 
       .line {
-        display: inline;
+        display: inline-block;
       }
     }
   }
@@ -53,18 +54,3 @@
     }
   }
 }
-
-.note-text .code {
-  border: none;
-  box-shadow: none;
-  background: $background-color;
-  padding: 1em;
-  overflow-x: auto;
-
-  code {
-    font-family: $monospace_font;
-    white-space: pre;
-    word-wrap: normal;
-    padding: 0;
-  }
-}
diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss
index 871b808bad4c4aca098f8aacfae64d3160dfba1f..d6cd78813c0aa164a41f9ee92b0b305ef7fbc5f2 100644
--- a/app/assets/stylesheets/framework/jquery.scss
+++ b/app/assets/stylesheets/framework/jquery.scss
@@ -53,3 +53,14 @@
     color: #333;
   }
 }
+
+.ui-sortable-handle {
+  cursor: move;
+  cursor: -webkit-grab;
+  cursor: -moz-grab;
+
+  &:active {
+    cursor: -webkit-grabbing;
+    cursor: -moz-grabbing;
+  }
+}
diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss
index 2cd30491bf5de471ee1c144a0f693bb98d7840e6..b6f21fd8c91a20904798eee5dadb77d1817c116c 100644
--- a/app/assets/stylesheets/framework/pagination.scss
+++ b/app/assets/stylesheets/framework/pagination.scss
@@ -1,35 +1,11 @@
 .gl-pagination {
+  text-align: center;
   border-top: 1px solid $border-color;
-  background-color: $background-color;
-  margin: -$gl-padding;
+  margin: 0;
   margin-top: 0;
 
   .pagination {
     padding: 0;
-    margin: 0;
-    display: block;
-
-    li.first,
-    li.last,
-    li.next,
-    li.prev {
-      > a {
-        color: $link-color;
-
-        &:hover {
-          color: #fff;
-        }
-      }
-    }
-
-    li > a,
-    li > span {
-      border: none;
-      margin: 0;
-      @include border-radius(0 !important);
-      padding: 13px 19px;
-      border-right: 1px solid $border-color;
-    }
   }
 }
 
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index 57b9451b264d95f6a5a66c52e51ee35d55930f84..ae7bdf14c40165fc8e21e9e95f4d2b64ef6a0dbb 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -2,7 +2,13 @@
   margin-bottom: $gl-padding;
 
   .panel-heading {
-    padding: 7px $gl-padding;
+    padding: $gl-vert-padding $gl-padding;
+    line-height: 36px;
+
+    .controls {
+      margin-top: -2px;
+      float: right;
+    }
   }
 
   .panel-body {
@@ -14,7 +20,3 @@
     }
   }
 }
-
-.container-blank .panel .panel-heading {
-  line-height: 42px !important;  
-}
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index c4e9f467ce4ea4ba73115ca5ba4b91de16232667..75b770ae5a275317887ed1f0cc53bf6af41abfd0 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -33,12 +33,12 @@ table {
         background-color: $background-color;
         font-weight: normal;
         font-size: 15px;
-        border-bottom: 1px solid $border-color !important;
+        border-bottom: 1px solid $border-color;
       }
 
       td {
-        border-color: $table-border-color !important;
-        border-bottom: 1px solid;
+        border-color: $table-border-color;
+        border-bottom: 1px solid $border-color;
       }
     }
   }
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 88072606bf58686a7a08155e396cc70d45b881db..3e709244879594f8ac841d854d922f9a353572f9 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -114,22 +114,9 @@
  *
  */
 
-.container-blank .panel .panel-heading {
-  font-size: 17px;
-  line-height: 38px;
-}
-
 .panel {
   box-shadow: none;
 
-  .panel-heading {
-    .panel-head-actions {
-      position: relative;
-      top: -5px;
-      float: right;
-    }
-  }
-
   .panel-body {
     form, pre {
       margin: 0;
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index cd0621cdbf308decc205e393aa5a5db4d3495e69..33270388e6462cf934d8cc4081502d1b45b38fba 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -22,9 +22,9 @@ $brand-info:     $gl-info;
 $brand-warning:  $gl-warning;
 $brand-danger:   $gl-danger;
 
-$border-radius-base:        2px !default;
-$border-radius-large:       2px !default;
-$border-radius-small:       2px !default;
+$border-radius-base:        3px !default;
+$border-radius-large:       3px !default;
+$border-radius-small:       3px !default;
 
 
 //== Scaffolding
@@ -66,20 +66,20 @@ $legend-color:                   $text-color;
 //##
 
 $pagination-color:                     $gl-gray;
-$pagination-bg:                        $background-color;
-$pagination-border:                    transparent;
+$pagination-bg:                        #fff;
+$pagination-border:                    $border-color;
 
-$pagination-hover-color:               #fff;
-$pagination-hover-bg:                  $brand-info;
-$pagination-hover-border:              transparent;
+$pagination-hover-color:               $gl-gray;
+$pagination-hover-bg:                  $hover;
+$pagination-hover-border:              $border-color;
 
-$pagination-active-color:              #fff;
-$pagination-active-bg:                 $brand-info;
-$pagination-active-border:             transparent;
+$pagination-active-color:              $blue-dark;
+$pagination-active-bg:                 #fff;
+$pagination-active-border:             $border-color;
 
-$pagination-disabled-color:            #fff;
-$pagination-disabled-bg:               lighten($brand-info, 15%);
-$pagination-disabled-border:           transparent;
+$pagination-disabled-color:            #cdcdcd;
+$pagination-disabled-bg:               $background-color;
+$pagination-disabled-border:           $border-color;
 
 
 //== Form states and alerts
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index ab4f71af039d1591c413d88449c544465a4761a3..8d8f41287da680f0e07d047d7a29b1d8ffe100f8 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -87,8 +87,8 @@
   }
 
   p {
-    color:#5c5d5e;
-    margin:6px 0 0 0;
+    color: #5c5d5e;
+    margin: 6px 0 0 0;
   }
 
   table {
@@ -102,11 +102,10 @@
   }
 
   pre {
-    margin: 12px 0 12px 0 !important;
-    background-color: #f8fafc;
-    font-size: 13px !important;
-    color: #5b6169;
-    line-height: 1.6em !important;
+    margin: 12px 0 12px 0;
+    font-size: 13px;
+    line-height: 1.6em;
+    overflow-x: auto;
     @include border-radius(2px);
   }
 
@@ -116,7 +115,7 @@
 
   ul, ol {
     padding: 0;
-    margin: 6px 0 6px 18px !important;
+    margin: 6px 0 6px 28px !important;
   }
 
   li {
@@ -204,11 +203,6 @@ h1, h2, h3, h4, h5, h6 {
 pre {
   font-family: $monospace_font;
 
-  &.dark {
-    background: #333;
-    color: $background-color;
-  }
-
   &.plain-readme {
     background: none;
     border: none;
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index 6a2b25ddc67905a29767100b831a84fe5a456be2..b794da2ce9805cd477eb62c9e95174754aca6014 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -1,18 +1,38 @@
 /* https://github.com/MozMorris/tomorrow-pygments */
 .code.dark {
+  // Line numbers
+  .line-numbers, .diff-line-num {
+    background-color: #1d1f21;
+  }
+
+  .diff-line-num, .diff-line-num a {
+    color: rgba(255, 255, 255, 0.3);
+  }
 
-  background-color: #1d1f21 !important;
-  color: #c5c8c6 !important;
+  // Code itself
+  pre.code, .diff-line-num {
+    border-color: #666;
+  }
 
-  pre.highlight,
-  .line-numbers,
-  .line-numbers a {
-    background-color: #1d1f21 !important;
-    color: #c5c8c6 !important;
+  &, pre.code, .line_holder .line_content {
+    background-color: #1d1f21;
+    color: #c5c8c6;
   }
 
-  pre.code {
-    border-left: 1px solid #666;
+  // Diff line
+  .line_holder {
+    .diff-line-num.new, .line_content.new {
+      @include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080);
+    }
+
+    .diff-line-num.old, .line_content.old {
+      @include diff_background(rgba(255, 51, 51, 0.2), rgba(255, 51, 51, 0.25), #808080);
+    }
+
+    .line_content.match {
+      color: rgba(255, 255, 255, 0.3);
+      background: rgba(255, 255, 255, 0.1);
+    }
   }
 
   // highlight line via anchor
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 8560c3c490fc1e3070148bb05828d97bbfee3ced..9098e07adcd793aad582ea01bf9b2e6c42837a29 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -1,18 +1,38 @@
 /* https://github.com/richleland/pygments-css/blob/master/monokai.css */
 .code.monokai {
+  // Line numbers
+  .line-numbers, .diff-line-num {
+    background-color: #272822;
+  }
+
+  .diff-line-num, .diff-line-num a {
+    color: rgba(255, 255, 255, 0.3);
+  }
 
-  background-color: #272822 !important;
-  color: #f8f8f2 !important;
+  // Code itself
+  pre.code, .diff-line-num {
+    border-color: #555;
+  }
 
-  pre.highlight,
-  .line-numbers,
-  .line-numbers a {
-    background-color :#272822 !important;
-    color: #f8f8f2 !important;
+  &, pre.code, .line_holder .line_content {
+    background-color: #272822;
+    color: #f8f8f2;
   }
 
-  pre.code {
-    border-left: 1px solid #555;
+  // Diff line
+  .line_holder {
+    .diff-line-num.new, .line_content.new {
+      @include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080);
+    }
+
+    .diff-line-num.old, .line_content.old {
+      @include diff_background(rgba(254, 147, 140, 0.15), rgba(254, 147, 140, 0.2), #808080);
+    }
+
+    .line_content.match {
+      color: rgba(255, 255, 255, 0.3);
+      background: rgba(255, 255, 255, 0.1);
+    }
   }
 
   // highlight line via anchor
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index 7d489a9666b8b0828a5b7f4bf7670b0d6cdb23a4..8b1a2824f76c78bffd35f8ddef42caa37f17241f 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -1,18 +1,38 @@
 /* https://gist.github.com/qguv/7936275 */
 .code.solarized-dark {
+  // Line numbers
+  .line-numbers, .diff-line-num {
+    background-color: #002b36;
+  }
+
+  .diff-line-num, .diff-line-num a {
+    color: rgba(255, 255, 255, 0.3);
+  }
 
-  background-color: #002b36 !important;
-  color: #93a1a1 !important;
+  // Code itself
+  pre.code, .diff-line-num {
+    border-color: #113b46;
+  }
 
-  pre.highlight,
-  .line-numbers,
-  .line-numbers a {
-    background-color: #002b36 !important;
-    color: #93a1a1 !important;
+  &, pre.code, .line_holder .line_content {
+    background-color: #002b36;
+    color: #93a1a1;
   }
 
-  pre.code {
-    border-left: 1px solid #113b46;
+  // Diff line
+  .line_holder {
+    .diff-line-num.new, .line_content.new {
+      @include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46);
+    }
+
+    .diff-line-num.old, .line_content.old {
+      @include diff_background(rgba(220, 50, 47, 0.3), rgba(220, 50, 47, 0.25), #113b46);
+    }
+
+    .line_content.match {
+      color: rgba(255, 255, 255, 0.3);
+      background: rgba(255, 255, 255, 0.1);
+    }
   }
 
   // highlight line via anchor
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index 200ed346446e1dc110c09563a2a872914323f564..7ad89dd2c7c3ae0acb7cf141ccdf93b831652658 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -1,18 +1,38 @@
 /* https://gist.github.com/qguv/7936275 */
 .code.solarized-light {
+  // Line numbers
+  .line-numbers, .diff-line-num {
+    background-color: #fdf6e3;
+  }
+
+  .diff-line-num, .diff-line-num a {
+    color: rgba(0, 0, 0, 0.3);
+  }
 
-  background-color: #fdf6e3 !important;
-  color: #586e75 !important;
+  // Code itself
+  pre.code, .diff-line-num {
+    border-color: #c5d0d4;
+  }
 
-  pre.highlight,
-  .line-numbers,
-  .line-numbers a {
-    background-color: #fdf6e3 !important;
-    color: #586e75 !important;
+  &, pre.code, .line_holder .line_content {
+    background-color: #fdf6e3;
+    color: #586e75;
   }
 
-  pre.code {
-    border-left: 1px solid #c5d0d4;
+  // Diff line
+  .line_holder {
+    .diff-line-num.new, .line_content.new {
+      @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4);
+    }
+
+    .diff-line-num.old, .line_content.old {
+      @include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.25), #c5d0d4);
+    }
+
+    .line_content.match {
+      color: rgba(0, 0, 0, 0.3);
+      background: rgba(255, 255, 255, 0.4);
+    }
   }
 
   // highlight line via anchor
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index e2626da78712858274a8fb233595f770ce765254..8a091028a6c6df8ea29739e3115c0a457d8b57a8 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -1,20 +1,60 @@
 /* https://github.com/aahan/pygments-github-style */
 .code.white {
+  // Line numbers
+  .line-numbers, .diff-line-num {
+    background-color: $background-color;
+  }
 
-  background-color: #f8fafc !important;
-  color: #5b6169 !important;
+  .diff-line-num, .diff-line-num a {
+    color: rgba(0, 0, 0, 0.3);
+  }
 
-  pre.highlight,
-  .line-numbers,
-  .line-numbers a {
-    background-color: $background-color !important;
-    color: $gl-gray !important;
+  // Code itself
+  pre.code, .diff-line-num {
+    border-color: $border-color;
   }
 
-  pre.code {
-    border-left: 1px solid $border-color;
-    background-color: #fff !important;
-    color: #333 !important;
+  &, pre.code, .line_holder .line_content {
+    background-color: #fff;
+    color: #333;
+  }
+
+  // Diff line
+  .line_holder {
+    .diff-line-num {
+      &.old {
+        background: #ffdddd;
+        border-color: #f1c0c0;
+      }
+
+      &.new {
+        background: #dbffdb;
+        border-color: #c1e9c1;
+      }
+    }
+
+    .line_content {
+      &.old {
+        background: #ffecec;
+
+        span.idiff {
+          background-color: #f8cbcb;
+        }
+      }
+
+      &.new {
+        background: #eaffea;
+
+        span.idiff {
+          background-color: #a6f3a6;
+        }
+      }
+
+      &.match {
+        color: rgba(0, 0, 0, 0.3);
+        background: #fafafa;
+      }
+    }
   }
 
   // highlight line via anchor
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 04e9a58e1cf4c919d15a9ede1b5d8d660ec9980e..a7925e795499e76698d33f4a628f911384f1c39c 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -32,16 +32,6 @@
     background: #FFF;
     color: #333;
 
-    .old {
-      span.idiff {
-        background-color: #f8cbcb;
-      }
-    }
-    .new {
-      span.idiff {
-        background-color: #a6f3a6;
-      }
-    }
     .unfold {
       cursor: pointer;
     }
@@ -76,7 +66,7 @@
     }
 
     tr.line_holder.parallel {
-      .old_line, .new_line, .diff_line {
+      .old_line, .new_line {
         min-width: 50px;
       }
 
@@ -85,14 +75,12 @@
       }
     }
 
-    .old_line, .new_line, .diff_line {
+    .old_line, .new_line {
       margin: 0px;
       padding: 0px;
       border: none;
-      background: $background-color;
-      color: rgba(0, 0, 0, 0.3);
       padding: 0px 5px;
-      border-right: 1px solid $border-color;
+      border-right: 1px solid;
       text-align: right;
       min-width: 35px;
       max-width: 50px;
@@ -102,48 +90,16 @@
         float: left;
         width: 35px;
         font-weight: normal;
-        color: rgba(0, 0, 0, 0.3);
         &:hover {
           text-decoration: underline;
         }
       }
-      &.new {
-        background: #CFD;
-      }
-      &.old {
-        background: #FDD;
-      }
-    }
-    .diff_line {
-      padding: 0;
-    }
-    .line_holder {
-      &.old .old_line,
-      &.old .new_line {
-        background: #ffdddd;
-        border-color: #f1c0c0;
-      }
-      &.new .old_line,
-      &.new .new_line {
-        background: #dbffdb;
-        border-color: #c1e9c1;
-      }
     }
     .line_content {
       display: block;
       margin: 0px;
       padding: 0px 0.5em;
       border: none;
-      &.new {
-        background: #eaffea;
-      }
-      &.old {
-        background: #ffecec;
-      }
-      &.matched {
-        color: $border-color;
-        background: #fafafa;
-      }
       &.parallel {
         display: table-cell;
       }
@@ -393,3 +349,15 @@
     right: 15px;
   }
 }
+
+@mixin diff_background($background, $idiff, $border) {
+  background: $background;
+
+  &.line_content span.idiff {
+    background: $idiff;
+  }
+
+  &.diff-line-num {
+    border-color: $border;
+  }
+}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 263993f59a5510eea684ea9ed201d09524fd0930..fdd86979a360f2c68ee42ef550258cd04d7869d9 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -1,5 +1,15 @@
 .member-search-form {
   float: left;
+
+  input[type='search'] {
+    width: 225px;
+    vertical-align: bottom;
+    
+    @media (max-width: $screen-xs-max) {
+      width: 100px;
+      vertical-align: bottom;
+    }
+  }
 }
 
 .milestone-row {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index ad92cc22815664622b3b13d75131e461970be180..dd6a251f811f74168cadefff142fb77c9b7aff44 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -49,11 +49,6 @@
 .issue-search-form {
   margin: 0;
   height: 24px;
-
-  .issue_search {
-    border: 1px solid #DDD !important;
-    background-color: #f4f4f4;
-  }
 }
 
 form.edit-issue {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 75f2ae80a92f49e07998c888a282e6acc2a23d05..f033ff15f881194de18202f67a7d5024d4aac43e 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -201,3 +201,39 @@
 .mr-source-target {
   line-height: 31px;
 }
+
+.disabled-comment-area {
+  padding: 16px 0;
+
+  .disabled-profile {
+    width: 40px;
+    height: 40px;
+    background: $border-gray-dark;
+    border-radius: 20px;
+    display: inline-block;
+    margin-right: 10px;
+  }
+
+  .disabled-comment {
+    background: $gray-light;
+    display: inline-block;
+    vertical-align: top;
+    height: 200px;
+    border-radius: 4px;
+    border: 1px solid $border-gray-normal;
+    padding-top: 90px;
+    text-align: center;
+    right: 20px;
+    position: absolute;
+    left: 70px;
+    margin-bottom: 20px;
+
+    span {
+      color: #B2B2B2;
+
+      a {
+        color: $md-link-color;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 2c9a42f9892eee20c5b23190b7886d354c301837..32ba1676333c217146028b70d6d7f46952cea275 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -10,18 +10,6 @@
   margin: 10px $gl-padding;
 }
 .diff-file .diff-content {
-  tr.line_holder:hover {
-    &> td.line_content {
-      background: $hover !important;
-      border-color: darken($hover, 10%) !important;
-    }
-    &> td.new_line,
-    &> td.old_line {
-      background: darken($hover, 4%) !important;
-      border-color: darken($hover, 10%) !important;
-    }
-  }
-
   tr.line_holder:hover > td .line_note_link {
     opacity: 1.0;
     filter: alpha(opacity=100);
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 72b0ed29a698b1d2e95b79c8c654f61784a2165f..19ead07c06ae159039a21dfbfa897e989e25176a 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -154,6 +154,7 @@ ul.notes {
       text-align: center;
       padding: 10px 0;
       background: #FFF;
+      color: $text-color;
     }
     &.notes_line2 {
       text-align: center;
@@ -242,11 +243,8 @@ ul.notes {
 
   // "show" the icon also if we just hover somewhere over the line
   &:hover > td {
-    background: $hover !important;
-
     .add-diff-note {
       @include show-add-diff-note;
     }
   }
 }
-
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 003a4c22f206af057a7d1d6407a83b93d35e0cd7..e4ea47cc4a215e53f8ee7721ab41781ffa54e82c 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -564,3 +564,53 @@ pre.light-well {
   color: #E62958;
   margin-top: 2px;
 }
+
+/*
+ * Forks list rendered on Project's forks page
+ */
+
+.forks-top-block {
+  padding: 16px 0;
+}
+
+.projects-search-form {
+  .dropdown-toggle.btn {
+    margin-top: -3px;
+  }
+
+  &.fork-search-form {
+    margin: 0;
+    margin-top: -$gl-padding;
+    padding-bottom: 0;
+
+    input {
+      /* Small devices (tablets, 768px and up) */
+      @media (min-width: $screen-sm-min) { width: 180px; }
+
+      /* Medium devices (desktops, 992px and up) */
+      @media (min-width: $screen-md-min) { width: 350px; }
+
+      /* Large devices (large desktops, 1200px and up) */
+      @media (min-width: $screen-lg-min) { width: 400px; }
+    }
+
+    .sort-forks {
+      width: 160px;
+    }
+
+    .fork-link {
+      float: right;
+      margin-left: $gl-padding;
+    }
+  }
+}
+
+.private-forks-notice .private-fork-icon {
+  i:nth-child(1) {
+    color: #2AA056;
+  }
+
+  i:nth-child(2) {
+    color: #FFFFFF;
+  }
+}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index bdcf18975220f3430603ee7c2f3458fd735b20bb..3aaa96da609172170cb7dc5907cf751e77d57cf1 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -3,10 +3,6 @@
     border-bottom: 1px solid #DDD;
     padding-bottom: 15px;
     margin-bottom: 15px;
-
-    .term {
-      height: 22px;
-    }
   }
 }
 
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 6a6dd7dfc853c8cf9b2d97b6d31e4ce8f7ccb9c5..c7411617cb3157d6c18b59328f92a28e05945f02 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -22,8 +22,6 @@
       &:hover {
         td {
           background: $hover;
-          border-top: 1px solid #ADF;
-          border-bottom: 1px solid #ADF;
         }
         cursor: pointer;
       }
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 91f7d78bd73184e0d84dd3a96388b21ed4e62329..9943745208ed86d979a48e86b685f874d4f970e6 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -77,6 +77,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
       :recaptcha_enabled,
       :recaptcha_site_key,
       :recaptcha_private_key,
+      :sentry_enabled,
+      :sentry_dsn,
       restricted_visibility_levels: [],
       import_sources: []
     )
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index bf99b2e777d2993c3ea73f18ee860d3bdd1ac4ae..824175c8a6c8f217ee428f8b3db84d50ad07a61c 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -15,6 +15,7 @@ class ApplicationController < ActionController::Base
   before_action :check_password_expiration
   before_action :check_2fa_requirement
   before_action :ldap_security_check
+  before_action :sentry_user_context
   before_action :default_headers
   before_action :add_gon_variables
   before_action :configure_permitted_parameters, if: :devise_controller?
@@ -24,6 +25,7 @@ class ApplicationController < ActionController::Base
 
   helper_method :abilities, :can?, :current_application_settings
   helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
+  helper_method :repository
 
   rescue_from Encoding::CompatibilityError do |exception|
     log_exception(exception)
@@ -41,6 +43,16 @@ class ApplicationController < ActionController::Base
 
   protected
 
+  def sentry_user_context
+    if Rails.env.production? && current_application_settings.sentry_enabled && current_user
+      Raven.user_context(
+        id: current_user.id,
+        email: current_user.email,
+        username: current_user.username,
+      )
+    end
+  end
+
   # From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example
   # https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
   def authenticate_user_from_token!
@@ -286,7 +298,8 @@ class ApplicationController < ActionController::Base
   end
 
   def set_filters_params
-    params[:sort] ||= 'id_desc'
+    set_default_sort
+
     params[:scope] = 'all' if params[:scope].blank?
     params[:state] = 'opened' if params[:state].blank?
 
@@ -393,4 +406,24 @@ class ApplicationController < ActionController::Base
 
     current_user.nil? && root_path == request.path
   end
+
+  private
+
+  def set_default_sort
+    key = if is_a_listing_page_for?('issues') || is_a_listing_page_for?('merge_requests')
+            'issuable_sort'
+          end
+
+    cookies[key]  = params[:sort] if key && params[:sort].present?
+    params[:sort] = cookies[key] if key
+    params[:sort] ||= 'id_desc'
+  end
+
+  def is_a_listing_page_for?(page_type)
+    controller_name, action_name = params.values_at(:controller, :action)
+
+    (controller_name == "projects/#{page_type}" && action_name == 'index') ||
+    (controller_name == 'groups' && action_name == page_type) ||
+    (controller_name == 'dashboard' && action_name == page_type)
+  end
 end
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 62127a090817e01355ec314399db321c0d378a56..b9eb0a22f881532f4bbc61a650b4f52ed72a7f72 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -97,7 +97,7 @@ module CreatesCommit
       # 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
+      @mr_target_branch = @ref
     end
   end
 end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 58e9049f158e0f7191f1cdcc0fac1bec293cb63b..0b7fcdf5e9e60e67df33ff468e51c1bdfe6f112f 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -36,7 +36,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
   private
 
   def load_events
-    @events = Event.in_projects(@projects.pluck(:id))
+    @events = Event.in_projects(@projects)
     @events = @event_filter.apply_filter(@events).with_associations
     @events = @events.limit(20).offset(params[:offset] || 0)
   end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 087da935087149bbf6d76bb39ca136388c4f3765..139e40db180ffee38c873eab2cccecc95d84b957 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -23,14 +23,14 @@ class DashboardController < Dashboard::ApplicationController
   protected
 
   def load_events
-    project_ids =
+    projects =
       if params[:filter] == "starred"
         current_user.starred_projects
       else
         current_user.authorized_projects
-      end.pluck(:id)
+      end
 
-    @events = Event.in_projects(project_ids)
+    @events = Event.in_projects(projects)
     @events = @event_filter.apply_filter(@events).with_associations
     @events = @events.limit(20).offset(params[:offset] || 0)
   end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index fb26a4e6fc335a3780394b5d8388ee32268999e6..ad6b3eae932031353a0093a953f7574630340024 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -2,17 +2,18 @@ class GroupsController < Groups::ApplicationController
   include IssuesAction
   include MergeRequestsAction
 
-  skip_before_action :authenticate_user!, only: [:show, :issues, :merge_requests]
   respond_to :html
-  before_action :group, except: [:new, :create]
+
+  skip_before_action :authenticate_user!, only: [:index, :show, :issues, :merge_requests]
+  before_action :group, except: [:index, :new, :create]
 
   # Authorize
-  before_action :authorize_read_group!, except: [:show, :new, :create, :autocomplete]
+  before_action :authorize_read_group!, except: [:index, :show, :new, :create, :autocomplete]
   before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects]
   before_action :authorize_create_group!, only: [:new, :create]
 
   # Load group projects
-  before_action :load_projects, except: [:new, :create, :projects, :edit, :update, :autocomplete]
+  before_action :load_projects, except: [:index, :new, :create, :projects, :edit, :update, :autocomplete]
   before_action :event_filter, only: :show
 
   layout :determine_layout
@@ -81,16 +82,13 @@ class GroupsController < Groups::ApplicationController
 
   def group
     @group ||= Group.find_by(path: params[:id])
+    @group || render_404
   end
 
   def load_projects
     @projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity.non_archived
   end
 
-  def project_ids
-    @projects.pluck(:id)
-  end
-
   # Dont allow unauthorized access to group
   def authorize_read_group!
     unless @group and (@projects.present? or can?(current_user, :read_group, @group))
@@ -123,7 +121,7 @@ class GroupsController < Groups::ApplicationController
   end
 
   def load_events
-    @events = Event.in_projects(project_ids)
+    @events = Event.in_projects(@projects)
     @events = event_filter.apply_filter(@events).with_associations
     @events = @events.limit(20).offset(params[:offset] || 0)
   end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 4cad98b8e98bdd7044ed442ea0622418e14f016a..4c13228fce9f1723afbcf5627498672369fe15fc 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -21,15 +21,16 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
   # We only find ourselves here
   # if the authentication to LDAP was successful.
   def ldap
-    @user = Gitlab::LDAP::User.new(oauth)
-    @user.save if @user.changed? # will also save new users
-    gl_user = @user.gl_user
-    gl_user.remember_me = params[:remember_me] if @user.persisted?
+    ldap_user = Gitlab::LDAP::User.new(oauth)
+    ldap_user.save if ldap_user.changed? # will also save new users
+
+    @user = ldap_user.gl_user
+    @user.remember_me = params[:remember_me] if ldap_user.persisted?
 
     # Do additional LDAP checks for the user filter and EE features
-    if @user.allowed?
-      log_audit_event(gl_user, with: :ldap)
-      sign_in_and_redirect(gl_user)
+    if ldap_user.allowed?
+      log_audit_event(@user, with: :ldap)
+      sign_in_and_redirect(@user)
     else
       flash[:alert] = "Access denied for your LDAP account."
       redirect_to new_user_session_path
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 6e91d9b4ad96791adb21a9288b2ac54857d63688..f3bfede4354afc025e408e88b8000a96a56cc4a1 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -13,10 +13,10 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
     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.'
+      flash.now[:alert] = 'You must enable Two-factor Authentication for 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)}."
+      flash.now[:alert] = "You must enable Two-factor Authentication for your account before #{l(grace_period_deadline)}."
     end
 
     @qr_code = build_qr_code
diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb
index 9ea518e6c857f8681e6fb6a6bd20ca6ef07abd20..f576d0be1fc099e3c16c4419b157e3817b1e8fcf 100644
--- a/app/controllers/projects/blame_controller.rb
+++ b/app/controllers/projects/blame_controller.rb
@@ -8,28 +8,6 @@ class Projects::BlameController < Projects::ApplicationController
 
   def show
     @blob = @repository.blob_at(@commit.id, @path)
-    @blame = group_blame_lines
-  end
-
-  def group_blame_lines
-    blame = Gitlab::Git::Blame.new(@repository, @commit.id, @path)
-
-    prev_sha = nil
-    groups = []
-    current_group = nil
-
-    blame.each do |commit, line|
-      if prev_sha && prev_sha == commit.sha
-        current_group[:lines] << line
-      else
-        groups << current_group if current_group.present?
-        current_group = { commit: commit, lines: [line] }
-      end
-
-      prev_sha = commit.sha
-    end
-
-    groups << current_group if current_group.present?
-    groups
+    @blame_groups = Gitlab::Blame.new(@blob, @commit).groups
   end
 end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 9045e668735d46e318d8fbbb17b8f3a8859e8455..495a432347e3e9f3df69e4c0025625e6f397f6bc 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -54,7 +54,9 @@ class Projects::BlobController < Projects::ApplicationController
     @content = params[:content]
     @blob.load_all_data!(@repository)
     diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
-    @diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/))
+    diff_lines = diffy.diff.scan(/.*\n/)[2..-1]
+    diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines)
+    @diff_lines = Gitlab::Diff::Highlight.new(diff_lines).highlight
 
     render layout: false
   end
@@ -67,9 +69,9 @@ class Projects::BlobController < Projects::ApplicationController
   end
 
   def diff
-    @blob.load_all_data!(@repository)
-    @form = UnfoldForm.new(params)
-    @lines = @blob.data.lines[@form.since - 1..@form.to - 1]
+    @form  = UnfoldForm.new(params)
+    @lines = Gitlab::Highlight.highlight_lines(repository, @ref, @path)
+    @lines = @lines[@form.since - 1..@form.to - 1]
 
     if @form.bottom?
       @match_line = ''
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 870f6795219a442b86d7cf702cb84e84f847154f..f5a169e5aa984847cc9435914d7745bdc5a88079 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -72,6 +72,7 @@ class Projects::CommitController < Projects::ApplicationController
       @diffs = commit.diffs
     end
 
+    @diff_refs = [commit.parent || commit, commit]
     @notes_count = commit.notes.count
 
     @statuses = ci_commit.statuses if ci_commit
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 5200d609cc98c458f19c438fa7c7630456e59573..7bbe75b39740f953e20be316c097f2fa11b34a39 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -21,7 +21,8 @@ class Projects::CompareController < Projects::ApplicationController
       @commits = Commit.decorate(compare_result.commits, @project)
       @diffs = compare_result.diffs
       @commit = @project.commit(head_ref)
-      @first_commit = @project.commit(base_ref)
+      @base_commit = @project.merge_base_commit(base_ref, head_ref)
+      @diff_refs = [@base_commit, @commit]
       @line_notes = []
     end
   end
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 750181f0c1932257d9e60fa465578f986a070d31..e61e01c4a590bd373b793241da328b2c4008c97a 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -3,6 +3,15 @@ class Projects::ForksController < Projects::ApplicationController
   before_action :require_non_empty_project
   before_action :authorize_download_code!
 
+  def index
+    @sort = params[:sort] || 'id_desc'
+    @all_forks = project.forks.includes(:creator).order_by(@sort)
+
+    @public_forks, @protected_forks = @all_forks.partition do |project|
+      can?(current_user, :read_project, project)
+    end
+  end
+
   def new
     @namespaces = current_user.manageable_namespaces
     @namespaces.delete(@project.namespace)
@@ -10,7 +19,7 @@ class Projects::ForksController < Projects::ApplicationController
 
   def create
     namespace = Namespace.find(params[:namespace_key])
-    
+
     @forked_project = namespace.projects.find_by(path: project.path)
     @forked_project = nil unless @forked_project && @forked_project.forked_from_project == project
 
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 8d8035ef5ff9a993b161358a66f1f13e80016367..07f355c35b1437dec6a33e2098e0c28e59b90cbc 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -1,8 +1,8 @@
 class Projects::ImportsController < Projects::ApplicationController
   # Authorize
   before_action :authorize_admin_project!
-  before_action :require_no_repo, except: :show
-  before_action :redirect_if_progress, except: :show
+  before_action :require_no_repo, only: [:new, :create]
+  before_action :redirect_if_progress, only: [:new, :create]
 
   def new
   end
@@ -24,11 +24,11 @@ class Projects::ImportsController < Projects::ApplicationController
   end
 
   def show
-    if @project.repository_exists? || @project.import_finished?
+    if @project.import_finished?
       if continue_params
         redirect_to continue_params[:to], notice: continue_params[:notice]
       else
-        redirect_to project_path(@project), notice: "The project was successfully forked."
+        redirect_to namespace_project_path(@project.namespace, @project), notice: finished_notice
       end
     elsif @project.import_failed?
       redirect_to new_namespace_project_import_path(@project.namespace, @project)
@@ -36,6 +36,7 @@ class Projects::ImportsController < Projects::ApplicationController
       if continue_params && continue_params[:notice_now]
         flash.now[:notice] = continue_params[:notice_now]
       end
+
       # Render
     end
   end
@@ -44,6 +45,7 @@ class Projects::ImportsController < Projects::ApplicationController
 
   def continue_params
     continue_params = params[:continue]
+
     if continue_params
       continue_params.permit(:to, :notice, :notice_now)
     else
@@ -51,8 +53,16 @@ class Projects::ImportsController < Projects::ApplicationController
     end
   end
 
+  def finished_notice
+    if @project.forked?
+      'The project was successfully forked.'
+    else
+      'The project was successfully imported.'
+    end
+  end
+
   def require_no_repo
-    if @project.repository_exists? && !@project.import_in_progress?
+    if @project.repository_exists?
       redirect_to(namespace_project_path(@project.namespace, @project))
     end
   end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index a6284a24223758dae533d730520f039beefb84f0..ed3050d59aafa73943aa6bfb50ae945e59503241 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -58,7 +58,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
   def diffs
     @commit = @merge_request.last_commit
-    @first_commit = @merge_request.first_commit
+    @base_commit = @merge_request.diff_base_commit
+
+    # MRs created before 8.4 don't have a diff_base_commit,
+    # but we need it for the "View file @ ..." link by deleted files
+    @base_commit ||= @merge_request.first_commit.parent || @merge_request.first_commit
 
     @comments_allowed = @reply_allowed = true
     @comments_target = {
@@ -102,7 +106,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     @source_project = merge_request.source_project
     @commits = @merge_request.compare_commits.reverse
     @commit = @merge_request.last_commit
-    @first_commit = @merge_request.first_commit
+    @base_commit = @merge_request.diff_base_commit
     @diffs = @merge_request.compare_diffs
 
     @ci_commit = @merge_request.ci_commit
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 6f1e186d4084d4eb4729a74b989785c48115ad6a..1b9dd5680432f38b8ff24e94124ef29e8917b399 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -11,11 +11,9 @@ class Projects::NotesController < Projects::ApplicationController
     notes_json = { notes: [], last_fetched_at: current_fetched_at }
 
     @notes.each do |note|
-      notes_json[:notes] << {
-        id: note.id,
-        html: note_to_html(note),
-        valid: note.valid?
-      }
+      next if note.cross_reference_not_visible_for?(current_user)
+
+      notes_json[:notes] << note_json(note)
     end
 
     render json: notes_json
@@ -25,7 +23,7 @@ class Projects::NotesController < Projects::ApplicationController
     @note = Notes::CreateService.new(project, current_user, note_params).execute
 
     respond_to do |format|
-      format.json { render_note_json(@note) }
+      format.json { render json: note_json(@note) }
       format.html { redirect_back_or_default }
     end
   end
@@ -34,7 +32,7 @@ class Projects::NotesController < Projects::ApplicationController
     @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
 
     respond_to do |format|
-      format.json { render_note_json(@note) }
+      format.json { render json: note_json(@note) }
       format.html { redirect_back_or_default }
     end
   end
@@ -99,6 +97,8 @@ class Projects::NotesController < Projects::ApplicationController
   end
 
   def note_to_discussion_html(note)
+    return unless note.for_diff_line?
+
     if params[:view] == 'parallel'
       template = "projects/notes/_diff_notes_with_reply_parallel"
       locals =
@@ -106,7 +106,7 @@ class Projects::NotesController < Projects::ApplicationController
           { notes_left: [note], notes_right: [] }
         else
           { notes_left: [], notes_right: [note] }
-       end
+        end
     else
       template = "projects/notes/_diff_notes_with_reply"
       locals = { notes: [note] }
@@ -131,9 +131,9 @@ class Projects::NotesController < Projects::ApplicationController
     )
   end
 
-  def render_note_json(note)
+  def note_json(note)
     if note.valid?
-      render json: {
+      {
         valid: true,
         id: note.id,
         discussion_id: note.discussion_id,
@@ -144,7 +144,7 @@ class Projects::NotesController < Projects::ApplicationController
         discussion_with_diff_html: note_to_discussion_with_diff_html(note)
       }
     else
-      render json: {
+      {
         valid: false,
         award: note.is_award,
         errors: note.errors
@@ -163,8 +163,6 @@ class Projects::NotesController < Projects::ApplicationController
     )
   end
 
-  private
-
   def find_current_user_notes
     @notes = NotesFinder.new.execute(project, current_user, params)
   end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 935f7d75c6af49435f5a3d7ce97a4f7676052622..4df5095bd94735cf45ae140b03fc92e1a525833e 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -93,6 +93,10 @@ class ProjectsController < ApplicationController
       return
     end
 
+    if @project.pending_delete?
+      flash[:alert] = "Project queued for delete."
+    end
+
     respond_to do |format|
       format.html do
         if @project.repository_exists?
@@ -120,8 +124,8 @@ class ProjectsController < ApplicationController
   def destroy
     return access_denied! unless can?(current_user, :remove_project, @project)
 
-    ::Projects::DestroyService.new(@project, current_user, {}).execute
-    flash[:alert] = "Project '#{@project.name}' was deleted."
+    ::Projects::DestroyService.new(@project, current_user, {}).pending_delete!
+    flash[:alert] = "Project '#{@project.name}' will be deleted."
 
     redirect_to dashboard_projects_path
   rescue Projects::DestroyService::DestroyError => ex
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 825f85199bef03a33d4daa608bf0f5c38c88c06d..44eb58e418b96052dc34474c68acc2fb2b3cf64a 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -2,6 +2,8 @@ class SessionsController < Devise::SessionsController
   include AuthenticatesWithTwoFactor
   include Recaptcha::ClientHelper
 
+  skip_before_action :check_2fa_requirement, only: [:destroy]
+
   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]
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f3a2723ee0d1d4b496aadeeea8570e89889f0509..a2458ad3be04525851e6037889fe5386442169f2 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -171,7 +171,7 @@ module ApplicationHelper
 
   def search_placeholder
     if @project && @project.persisted?
-      'Search in this project'
+      'Search'
     elsif @snippet || @snippets || @show_snippets
       'Search snippets'
     elsif @group && @group.persisted?
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index d31d4cde08f4dcb2579d2fd582c50111401e9bca..694c03206bd6c8e452b66fb9f20b73fa0b5cde95 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -1,21 +1,10 @@
 module BlobHelper
-  def highlight(blob_name, blob_content, nowrap: false, continue: false)
-    @formatter ||= Rouge::Formatters::HTMLGitlab.new(
-      nowrap: nowrap,
-      cssclass: 'code highlight',
-      lineanchors: true,
-      lineanchorsid: 'LC'
-    )
-
-    begin
-      @lexer ||= Rouge::Lexer.guess(filename: blob_name, source: blob_content).new
-      result = @formatter.format(@lexer.lex(blob_content, continue: continue)).html_safe
-    rescue
-      @lexer = Rouge::Lexers::PlainText
-      result = @formatter.format(@lexer.lex(blob_content)).html_safe
-    end
+  def highlighter(blob_name, blob_content, nowrap: false)
+    Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap)
+  end
 
-    result
+  def highlight(blob_name, blob_content, nowrap: false)
+    Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap)
   end
 
   def no_highlight_files
@@ -37,20 +26,19 @@ module BlobHelper
                                      tree_join(ref, path),
                                      link_opts)
 
-    if !on_top_of_branch?
+    if !on_top_of_branch?(project, ref)
       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_edit_blob?(blob, project, ref)
+      link_to "Edit", edit_path, class: 'btn'
     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)
+      fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
 
-      link_to "Edit", fork_path, class: 'btn btn-small', method: :post
+      link_to "Edit", fork_path, class: 'btn', method: :post
     end
   end
 
@@ -61,11 +49,11 @@ module BlobHelper
 
     return unless blob
 
-    if !on_top_of_branch?
+    if !on_top_of_branch?(project, ref)
       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)
+    elsif can_edit_blob?(blob, project, ref)
       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 = {
@@ -73,8 +61,7 @@ module BlobHelper
         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)
+      fork_path = namespace_project_forks_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
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 590d20ac7b3495f37611935c015db301f2fdbce4..1d14ee52cfcc960080677c83da22dd2959d8ffae 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -152,7 +152,7 @@ module CommitsHelper
 
     options = {
       class: "commit-#{options[:source]}-link has_tooltip",
-      data: { :'original-title' => sanitize(source_email) }
+      data: { 'original-title'.to_sym => sanitize(source_email) }
     }
 
     if user.nil?
@@ -166,7 +166,7 @@ module CommitsHelper
     link_to(
       namespace_project_blob_path(project.namespace, project,
                                   tree_join(commit_sha, diff.new_path)),
-      class: 'btn btn-small view-file js-view-file'
+      class: 'btn view-file js-view-file'
     ) do
       raw('View file @') + content_tag(:span, commit_sha[0..6],
                                        class: 'commit-short-id')
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 24134310fc5d084b4cfa15f1e1c6009b9c51f476..f9bacc8ba45ee102dc82c7c139e7fcdb5335fc16 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -1,4 +1,13 @@
 module DiffHelper
+  def mark_inline_diffs(old_line, new_line)
+    old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_line, new_line).inline_diffs
+
+    marked_old_line = Gitlab::Diff::InlineDiffMarker.new(old_line).mark(old_diffs)
+    marked_new_line = Gitlab::Diff::InlineDiffMarker.new(new_line).mark(new_diffs)
+
+    [marked_old_line, marked_new_line]
+  end
+
   def diff_view
     params[:view] == 'parallel' ? 'parallel' : 'inline'
   end
@@ -19,13 +28,13 @@ module DiffHelper
     end
   end
 
-  def safe_diff_files(diffs)
+  def safe_diff_files(diffs, diff_refs)
     lines = 0
     safe_files = []
     diffs.first(allowed_diff_size).each do |diff|
       lines += diff.diff.lines.count
       break if lines > allowed_diff_lines
-      safe_files << Gitlab::Diff::File.new(diff)
+      safe_files << Gitlab::Diff::File.new(diff, diff_refs)
     end
     safe_files
   end
@@ -43,64 +52,6 @@ module DiffHelper
     Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
   end
 
-  def parallel_diff(diff_file, index)
-    lines = []
-    skip_next = false
-
-    # Building array of lines
-    #
-    # [
-    # left_type, left_line_number, left_line_content, left_line_code,
-    # right_line_type, right_line_number, right_line_content, right_line_code
-    # ]
-    #
-    diff_file.diff_lines.each do |line|
-
-      full_line = line.text
-      type = line.type
-      line_code = generate_line_code(diff_file.file_path, line)
-      line_new = line.new_pos
-      line_old = line.old_pos
-
-      next_line = diff_file.next_line(line.index)
-
-      if next_line
-        next_line_code = generate_line_code(diff_file.file_path, next_line)
-        next_type = next_line.type
-        next_line = next_line.text
-      end
-
-      if type == 'match' || type.nil?
-        # line in the right panel is the same as in the left one
-        line = [type, line_old, full_line, line_code, type, line_new, full_line, line_code]
-        lines.push(line)
-      elsif type == 'old'
-        if next_type == 'new'
-          # Left side has text removed, right side has text added
-          line = [type, line_old, full_line, line_code, next_type, line_new, next_line, next_line_code]
-          lines.push(line)
-          skip_next = true
-        elsif next_type == 'old' || next_type.nil?
-          # Left side has text removed, right side doesn't have any change
-          # No next line code, no new line number, no new line text
-          line = [type, line_old, full_line, line_code, next_type, nil, "&nbsp;", nil]
-          lines.push(line)
-        end
-      elsif type == 'new'
-        if skip_next
-          # Change has been already included in previous line so no need to do it again
-          skip_next = false
-          next
-        else
-          # Change is only on the right side, left side has no change
-          line = [nil, nil, "&nbsp;", line_code, type, line_new, full_line, line_code]
-          lines.push(line)
-        end
-      end
-    end
-    lines
-  end
-
   def unfold_bottom_class(bottom)
     (bottom) ? 'js-unfold-bottom' : ''
   end
@@ -111,7 +62,7 @@ module DiffHelper
 
   def diff_line_content(line)
     if line.blank?
-      " &nbsp;"
+      " &nbsp;".html_safe
     else
       line
     end
@@ -160,8 +111,7 @@ module DiffHelper
 
   def commit_for_diff(diff)
     if diff.deleted_file
-      first_commit = @first_commit || @commit
-      first_commit.parent || @first_commit
+      @base_commit || @commit.parent || @commit
     else
       @commit
     end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 5724d3aabecd861796d0d81a164822f79cddd8af..84c6d0883b0f5dc621dd413ec5e0f4caf4cb56e4 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -7,7 +7,7 @@ module IconsHelper
   # font-awesome-rails gem, but should we ever use a different icon pack in the
   # future we won't have to change hundreds of method calls.
   def icon(names, options = {})
-    fa_icon(names, options)
+    options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
   end
 
   def spinner(text = nil, visible = false)
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index a2c3d4d2f327d2aba74684daba41300b5f8c233a..92eac0560bd998d980fb88e9564f571b8fbcd546 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -83,7 +83,11 @@ module LabelsHelper
   end
 
   def text_color_for_bg(bg_color)
-    r, g, b = bg_color.slice(1,7).scan(/.{2}/).map(&:hex)
+    if bg_color.length == 4
+      r, g, b = bg_color[1, 4].scan(/./).map { |v| (v * 2).hex }
+    else
+      r, g, b = bg_color[1, 7].scan(/.{2}/).map(&:hex)
+    end
 
     if (r + g + b) > 500
       '#333333'
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 77ba612548a11785dbb214bbd2e8026e9c879946..8c8b355028c62e7bf7483d39166e05cb97c04382 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -40,7 +40,7 @@ module ProjectsHelper
       link_to(author_html, user_path(author), class: "author_link").html_safe
     else
       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
+      link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
     end
   end
 
@@ -116,7 +116,7 @@ module ProjectsHelper
   private
 
   def get_project_nav_tabs(project, current_user)
-    nav_tabs = [:home]
+    nav_tabs = [:home, :forks]
 
     if !project.empty_repo? && can?(current_user, :download_code, project)
       nav_tabs << [:files, :commits, :network, :graphs]
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 906cb12cd48f911c0e1fde91be1a577315c682e7..bc36434f5492c7250e72b82cc72175d225a97dba 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -17,4 +17,79 @@ module SnippetsHelper
       snippet_path(snippet)
     end
   end
+
+  # Get an array of line numbers surrounding a matching
+  # line, bounded by min/max.
+  #
+  # @returns Array of line numbers
+  def bounded_line_numbers(line, min, max, surrounding_lines)
+    lower = line - surrounding_lines > min ? line - surrounding_lines : min
+    upper = line + surrounding_lines < max ? line + surrounding_lines : max
+    (lower..upper).to_a
+  end
+
+  # Returns a sorted set of lines to be included in a snippet preview.
+  # This ensures matching adjacent lines do not display duplicated
+  # surrounding code.
+  #
+  # @returns Array, unique and sorted.
+  def matching_lines(lined_content, surrounding_lines)
+    used_lines = []
+    lined_content.each_with_index do |line, line_number|
+      used_lines.concat bounded_line_numbers(
+        line_number,
+        0,
+        lined_content.size,
+        surrounding_lines
+      ) if line.include?(query)
+    end
+
+    used_lines.uniq.sort
+  end
+
+  # 'Chunkify' entire snippet.  Splits the snippet data into matching lines +
+  # surrounding_lines() worth of unmatching lines.
+  #
+  # @returns a hash with {snippet_object, snippet_chunks:{data,start_line}}
+  def chunk_snippet(snippet, surrounding_lines = 3)
+    lined_content = snippet.content.split("\n")
+    used_lines = matching_lines(lined_content, surrounding_lines)
+
+    snippet_chunk = []
+    snippet_chunks = []
+    snippet_start_line = 0
+    last_line = -1
+
+    # Go through each used line, and add consecutive lines as a single chunk
+    # to the snippet chunk array.
+    used_lines.each do |line_number|
+      if last_line < 0
+        # Start a new chunk.
+        snippet_start_line = line_number
+        snippet_chunk << lined_content[line_number]
+      elsif last_line == line_number - 1
+        # Consecutive line, continue chunk.
+        snippet_chunk << lined_content[line_number]
+      else
+        # Non-consecutive line, add chunk to chunk array.
+        snippet_chunks << {
+          data: snippet_chunk.join("\n"),
+          start_line: snippet_start_line + 1
+        }
+
+        # Start a new chunk.
+        snippet_chunk = [lined_content[line_number]]
+        snippet_start_line = line_number
+      end
+      last_line = line_number
+    end
+    # Add final chunk to chunk array
+    snippet_chunks << {
+      data: snippet_chunk.join("\n"),
+      start_line: snippet_start_line + 1
+    }
+
+    # Return snippet with chunk array
+    { snippet_object: snippet, snippet_chunks: snippet_chunks }
+  end
 end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 6c6c2468374deb48ce0e3d50c00154f33309a683..59563b8823c8d350811b96a0ab7af5fd61c097ea 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -41,6 +41,8 @@
 #  recaptcha_site_key                :string
 #  recaptcha_private_key             :string
 #  metrics_port                      :integer          default(8089)
+#  sentry_enabled                    :boolean          default(FALSE)
+#  sentry_dsn                        :string
 #
 
 class ApplicationSetting < ActiveRecord::Base
@@ -82,6 +84,10 @@ class ApplicationSetting < ActiveRecord::Base
             presence: true,
             if: :recaptcha_enabled
 
+  validates :sentry_dsn,
+            presence: true,
+            if: :sentry_enabled
+
   validates_each :restricted_visibility_levels do |record, attr, value|
     unless value.nil?
       value.each do |level|
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 611196337170ad2fb8b12c07f7c90fa697179f26..8a0a8a4c2a9b846936574a68e5e7c12835169714 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -26,7 +26,9 @@ class BroadcastMessage < ActiveRecord::Base
   default_value_for :font,  '#FFFFFF'
 
   def self.current
-    where("ends_at > :now AND starts_at <= :now", now: Time.zone.now).last
+    Rails.cache.fetch("broadcast_message_current", expires_in: 1.minute) do
+      where("ends_at > :now AND starts_at <= :now", now: Time.zone.now).last
+    end
   end
 
   def active?
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 16a5b03f5915c7c28e03f2fcd252257dd97064b3..623edd8bc573921d3e5e19d83cfde0b3b787d086 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -346,17 +346,17 @@ module Ci
     end
 
     def artifacts_browse_url
-      if artifacts_browser_supported?
+      if artifacts_metadata?
         browse_namespace_project_build_artifacts_path(project.namespace, project, self)
       end
     end
 
-    def artifacts_browser_supported?
+    def artifacts_metadata?
       artifacts? && artifacts_metadata.exists?
     end
 
-    def artifacts_metadata_entry(path)
-      Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path).to_entry
+    def artifacts_metadata_entry(path, **options)
+      Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry
     end
 
     private
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 0ba7b584d91191983c3b2a6f4bdbf5ef71586f02..23b771aebb7694bcc274b61c7ab2e7a17c411570 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -68,18 +68,18 @@ class Commit
 
   # Pattern used to extract commit references from text
   #
-  # The SHA can be between 6 and 40 hex characters.
+  # The SHA can be between 7 and 40 hex characters.
   #
   # This pattern supports cross-project references.
   def self.reference_pattern
     %r{
       (?:#{Project.reference_pattern}#{reference_prefix})?
-      (?<commit>\h{6,40})
+      (?<commit>\h{7,40})
     }x
   end
 
   def self.link_reference_pattern
-    super("commit", /(?<commit>\h{6,40})/)
+    super("commit", /(?<commit>\h{7,40})/)
   end
 
   def to_reference(from_project = nil)
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 14e7971fa066175d79e5cdff056158ce5687698e..289dbc57287741ec06b7e4b2a095f03593524358 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -32,8 +32,8 @@ class CommitRange
   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}/
+  # between 7 and 40 hex characters.
+  STRICT_PATTERN = /\h{7,40}\.{2,3}\h{7,40}/
 
   def self.reference_prefix
     '@'
diff --git a/app/models/event.rb b/app/models/event.rb
index 01d008035a585875f247bfa62274a7cffdc17d9a..4be23a1cf726a86af809dffc006c0b9a3f666f46 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -47,7 +47,11 @@ class Event < ActiveRecord::Base
   # Scopes
   scope :recent, -> { reorder(id: :desc) }
   scope :code_push, -> { where(action: PUSHED) }
-  scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent }
+
+  scope :in_projects, ->(projects) do
+    where(project_id: projects.select(:id).reorder(nil)).recent
+  end
+
   scope :with_associations, -> { includes(project: :namespace) }
   scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
 
@@ -64,12 +68,6 @@ class Event < ActiveRecord::Base
             [Event::CREATED, Event::CLOSED, Event::MERGED])
     end
 
-    def latest_update_time
-      row = select(:updated_at, :project_id).reorder(id: :desc).take
-
-      row ? row.updated_at : nil
-    end
-
     def limit_recent(limit = 20, offset = nil)
       recent.limit(limit).offset(offset)
     end
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 49f6c95e04586bf2e2d82a8861ed83ed72e9f43b..2ca79df0a29a9aeecbe5a3602fe28506d6daac75 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -31,7 +31,7 @@ class ExternalIssue
 
   # Pattern used to extract `JIRA-123` issue references from text
   def self.reference_pattern
-    %r{(?<issue>([A-Z\-]+-)\d+)}
+    %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
   end
 
   def to_reference(_from_project = nil)
diff --git a/app/models/group.rb b/app/models/group.rb
index 5a31b46920ca174c736151ec0cacd38cbd0a1f94..76042b3e3fd341ef46c1e8bc3d2cf0acd13d0aa1 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -19,7 +19,7 @@ require 'file_size_validator'
 class Group < Namespace
   include Gitlab::ConfigHelper
   include Referable
-  
+
   has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
   alias_method :members, :group_members
   has_many :users, through: :group_members
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 7beba9846089b51f6d6263fbc29af6f6a35e97aa..5f58c0508fd0279966120d0e5bc7096c850dd4e9 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -38,6 +38,7 @@ class Issue < ActiveRecord::Base
 
   scope :cared, ->(user) { where(assignee_id: user) }
   scope :open_for, ->(user) { opened.assigned_to(user) }
+  scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
 
   state_machine :state, initial: :opened do
     event :close do
diff --git a/app/models/member.rb b/app/models/member.rb
index 28aee2e379963224b4459c4b5251d10464528d63..34efcd0088d006722958d0ff2c0003bcb4935cf2 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -91,7 +91,7 @@ class Member < ActiveRecord::Base
         member.invite_email = user
       end
 
-      if can_update_member?(current_user, member)
+      if can_update_member?(current_user, member) || project_creator?(member, access_level)
         member.created_by ||= current_user
         member.access_level = access_level
 
@@ -107,6 +107,11 @@ class Member < ActiveRecord::Base
         current_user.can?(:update_group_member, member) ||
         current_user.can?(:update_project_member, member)
     end
+
+    def project_creator?(member, access_level)
+      member.new_record? && member.owner? &&
+        access_level.to_i == ProjectMember::MASTER
+    end
   end
 
   def invite?
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 1b0c76917aa377d47abb47c2be4716fe5c8a9976..560d1690e1432de9698378c9515eddccc60d81a9 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -84,7 +84,7 @@ class ProjectMember < Member
     def truncate_teams(project_ids)
       ProjectMember.transaction do
         members = ProjectMember.where(source_id: project_ids)
-        
+
         members.each do |member|
           member.destroy
         end
@@ -133,13 +133,13 @@ class ProjectMember < Member
       event_service.join_project(self.project, self.user)
       notification_service.new_project_member(self)
     end
-    
+
     super
   end
 
   def post_update_hook
     if access_level_changed?
-      notification_service.update_project_member(self) 
+      notification_service.update_project_member(self)
     end
 
     super
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index c63d0c016537099a8ceaed35a26aa9bd64518cc0..0af606455455891d2fced43c44278be0510df5bb 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -180,6 +180,14 @@ class MergeRequest < ActiveRecord::Base
     merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
   end
 
+  def diff_base_commit
+    if merge_request_diff
+      merge_request_diff.base_commit
+    elsif source_sha
+      self.target_project.merge_base_commit(self.source_sha, self.target_branch)
+    end
+  end
+
   def last_commit_short_sha
     last_commit.short_id
   end
@@ -254,7 +262,7 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def mergeable?
-    return false unless open? && !work_in_progress?
+    return false unless open? && !work_in_progress? && !broken?
 
     check_if_can_be_merged
 
@@ -477,12 +485,11 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def target_sha
-    @target_sha ||= target_project.
-      repository.commit(target_branch).sha
+    @target_sha ||= target_project.repository.commit(target_branch).sha
   end
 
   def source_sha
-    commits.first.sha
+    last_commit.try(:sha)
   end
 
   def fetch_ref
@@ -517,4 +524,10 @@ class MergeRequest < ActiveRecord::Base
   def ci_commit
     @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
   end
+
+  def diff_refs
+    return nil unless diff_base_commit
+
+    [diff_base_commit, last_commit]
+  end
 end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index c499a4b5b4c8006f834fcf3ffcaee513d89120ec..c95179d60461ea8055ff2c85937647d2031c802f 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -48,14 +48,11 @@ class MergeRequestDiff < ActiveRecord::Base
   end
 
   def diffs_no_whitespace
-    # Get latest sha of branch from source project
-    source_sha = merge_request.source_project.commit(source_branch).sha
-
     compare_result = Gitlab::CompareResult.new(
       Gitlab::Git::Compare.new(
-        merge_request.target_project.repository.raw_repository,
-        merge_request.target_branch,
-        source_sha,
+        self.repository.raw_repository,
+        self.target_branch,
+        self.source_sha,
       ), { ignore_whitespace_change: true }
     )
     @diffs_no_whitespace ||= load_diffs(dump_commits(compare_result.diffs))
@@ -73,12 +70,16 @@ class MergeRequestDiff < ActiveRecord::Base
     commits.last
   end
 
+  def base_commit
+    return nil unless self.base_commit_sha
+
+    merge_request.target_project.commit(self.base_commit_sha)
+  end
+
   def last_commit_short_sha
     @last_commit_short_sha ||= last_commit.short_id
   end
 
-  private
-
   def dump_commits(commits)
     commits.map(&:to_hash)
   end
@@ -156,6 +157,9 @@ class MergeRequestDiff < ActiveRecord::Base
     end
 
     self.st_diffs = new_diffs
+
+    self.base_commit_sha = self.repository.merge_base(self.source_sha, self.target_branch)
+
     self.save
   end
 
@@ -172,7 +176,10 @@ class MergeRequestDiff < ActiveRecord::Base
     merge_request.target_project.repository
   end
 
-  private
+  def source_sha
+    source_commit = merge_request.source_project.commit(source_branch)
+    source_commit.try(:sha)
+  end
 
   def compare_result
     @compare_result ||=
@@ -180,15 +187,11 @@ class MergeRequestDiff < ActiveRecord::Base
         # Update ref for merge request
         merge_request.fetch_ref
 
-        # Get latest sha of branch from source project
-        source_commit = merge_request.source_project.commit(source_branch)
-        source_sha = source_commit.try(:sha)
-
         Gitlab::CompareResult.new(
           Gitlab::Git::Compare.new(
-            merge_request.target_project.repository.raw_repository,
-            merge_request.target_branch,
-            source_sha,
+            self.repository.raw_repository,
+            self.target_branch,
+            self.source_sha
           )
         )
       end
diff --git a/app/models/note.rb b/app/models/note.rb
index 3e1375e5ad6371510cdea40afd9dcc1cd028a835..55255d22c2f4f3250f931b127c80cd28ea3a7a2c 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -33,7 +33,7 @@ class Note < ActiveRecord::Base
   participant :author
 
   belongs_to :project
-  belongs_to :noteable, polymorphic: true
+  belongs_to :noteable, polymorphic: true, touch: true
   belongs_to :author, class_name: "User"
   belongs_to :updated_by, class_name: "User"
 
@@ -244,7 +244,7 @@ class Note < ActiveRecord::Base
     prev_match_line = nil
     prev_lines = []
 
-    diff_lines.each do |line|
+    highlighted_diff_lines.each do |line|
       if line.type == "match"
         prev_lines.clear
         prev_match_line = line
@@ -261,7 +261,11 @@ class Note < ActiveRecord::Base
   end
 
   def diff_lines
-    @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a)
+    @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines)
+  end
+
+  def highlighted_diff_lines
+    Gitlab::Diff::Highlight.new(diff_lines).highlight
   end
 
   def discussion_id
diff --git a/app/models/project.rb b/app/models/project.rb
index 5579710a47666a98a97e9b49f539b66157fa6088..043f08b9a13a36f4245f9a8aa0f6daeb934b4614 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -36,6 +36,7 @@
 #  build_coverage_regex   :string
 #  build_allow_git_fetch  :boolean          default(TRUE), not null
 #  build_timeout          :integer          default(3600), not null
+#  pending_delete         :boolean
 #
 
 require 'carrierwave/orm/activerecord'
@@ -348,6 +349,11 @@ class Project < ActiveRecord::Base
     repository.commit(id)
   end
 
+  def merge_base_commit(first_commit_id, second_commit_id)
+    sha = repository.merge_base(first_commit_id, second_commit_id)
+    repository.commit(sha) if sha
+  end
+
   def saved?
     id && persisted?
   end
@@ -904,4 +910,8 @@ class Project < ActiveRecord::Base
   def runners_token
     ensure_runners_token!
   end
+
+  def wiki
+    @wiki ||= ProjectWiki.new(self, self.owner)
+  end
 end
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 8ce47495971b97d25225e5a1a42c4d5a3f92f500..c847eba8d1c9b340fef154c1c4391f7f81f8d7bf 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -12,6 +12,7 @@ class ProjectWiki
   # Returns a string describing what went wrong after
   # an operation fails.
   attr_reader :error_message
+  attr_reader :project
 
   def initialize(project, user = nil)
     @project = project
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d9ff71c01eddf5b7067fa9130892e0cee16b3033..130daddd9d11eba4407b7cedc9e208275058b666 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -57,7 +57,7 @@ class Repository
   # This method return true if repository contains some content visible in project page.
   #
   def has_visible_content?
-    !raw_repository.branches.empty?
+    raw_repository.branch_count > 0
   end
 
   def commit(id = 'HEAD')
@@ -589,6 +589,8 @@ class Repository
 
   def merge_base(first_commit_id, second_commit_id)
     rugged.merge_base(first_commit_id, second_commit_id)
+  rescue Rugged::ReferenceError
+    nil
   end
 
   def is_ancestor?(ancestor_id, descendant_id)
@@ -598,7 +600,7 @@ class Repository
 
   def search_files(query, ref)
     offset = 2
-    args = %W(#{Gitlab.config.git.bin_path} grep -i -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
+    args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
     Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
   end
 
diff --git a/app/models/tree.rb b/app/models/tree.rb
index ecee54c3e0aef0b247ee8fe3b9e0999b3293fc4b..7c4ed6e393b907a7b8e2298b24b164ab74dd0fc1 100644
--- a/app/models/tree.rb
+++ b/app/models/tree.rb
@@ -17,12 +17,20 @@ class Tree
   def readme
     return @readme if defined?(@readme)
 
-    # Take the first previewable readme, or return nil if none is available or
-    # we can't preview any of them
-    readme_tree = blobs.find do |blob|
-      blob.readme? && (previewable?(blob.name) || plain?(blob.name))
+    available_readmes = blobs.select(&:readme?)
+
+    previewable_readmes = available_readmes.select do |blob|
+      previewable?(blob.name)
+    end
+
+    plain_readmes = available_readmes.select do |blob|
+      plain?(blob.name)
     end
 
+    # Prioritize previewable over plain readmes
+    readme_tree = previewable_readmes.first || plain_readmes.first
+
+    # Return if we can't preview any of them
     if readme_tree.nil?
       return @readme = nil
     end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 2a65f0431c4323efac3a4a268df30d9296e8419e..dbd70dc5a44fff7f10cee1a390ad87edb292e27f 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -110,7 +110,7 @@ class WikiPage
   # Returns boolean True or False if this instance
   # is an old version of the page.
   def historical?
-    @page.historical?
+    @page.historical? && versions.first.sha != version.sha
   end
 
   # Returns boolean True or False if this instance
diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb
index e622fd5ea5d1e5f95bbb40a3334bd718eaa33567..173e50c9206ddd31f317d00ed88cfd9c2738c5fe 100644
--- a/app/services/delete_user_service.rb
+++ b/app/services/delete_user_service.rb
@@ -13,7 +13,7 @@ class DeleteUserService
       user.personal_projects.each do |project|
         # Skip repository removal because we remove directory with namespace
         # that contain all this repositories
-        ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
+        ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete!
       end
 
       user.destroy
diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb
index d929a6762933d0d928eb1bc170186a3dd4610dad..9189de390a2bfae5d723f0c386045dd11aa5f96b 100644
--- a/app/services/destroy_group_service.rb
+++ b/app/services/destroy_group_service.rb
@@ -9,7 +9,7 @@ class DestroyGroupService
     @group.projects.each do |project|
       # Skip repository removal because we remove directory with namespace
       # that contain all this repositories
-      ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
+      ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete!
     end
 
     @group.destroy
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index a8486e6a5a1ecb3c62f7d507e666d9eee9e2943a..8d9661167b522483ea89eb75f16010243fcfdb27 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -6,27 +6,12 @@ module Notes
       note.system = false
 
       if note.save
-        notification_service.new_note(note)
-
-        # Skip system notes, like status changes and cross-references and awards
-        unless note.system || note.is_award
-          event_service.leave_note(note, note.author)
-          note.create_cross_references!
-          execute_hooks(note)
-        end
+        # Finish the harder work in the background
+        NewNoteWorker.perform_in(2.seconds, note.id, params)
       end
 
       note
     end
 
-    def hook_data(note)
-      Gitlab::NoteDataBuilder.build(note, current_user)
-    end
-
-    def execute_hooks(note)
-      note_data = hook_data(note)
-      note.project.execute_hooks(note_data, :note_hooks)
-      note.project.execute_services(note_data, :note_hooks)
-    end
   end
 end
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f37d3c50cdd145470bc9bb7363f97cd7a40d8d26
--- /dev/null
+++ b/app/services/notes/post_process_service.rb
@@ -0,0 +1,30 @@
+module Notes
+  class PostProcessService
+
+    attr_accessor :note
+
+    def initialize(note)
+      @note = note
+    end
+
+    def execute
+      # Skip system notes, like status changes and cross-references and awards
+      unless @note.system || @note.is_award
+        EventCreateService.new.leave_note(@note, @note.author)
+        @note.create_cross_references!
+        execute_note_hooks
+      end
+    end
+
+    def hook_data
+      Gitlab::NoteDataBuilder.build(@note, @note.author)
+    end
+
+    def execute_note_hooks
+      note_data = hook_data
+      @note.project.execute_hooks(note_data, :note_hooks)
+      @note.project.execute_services(note_data, :note_hooks)
+    end
+
+  end
+end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index c94d7ab710f4feff827152790acc82502d3e1ba0..a6820183bee9ed83a3d94a3b9cb20933f9a00ec8 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -95,7 +95,7 @@ module Projects
       system_hook_service.execute_hooks_for(@project, :create)
 
       unless @project.group
-        @project.team << [current_user, :master]
+        @project.team << [current_user, :master, current_user]
       end
 
       @project.import_start if @project.import?
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 28872c89259d4f939d31d071cdab00f5f161e71b..294157b4f0e4edbeaf3c231e0c8e7e72d0e84358 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -6,6 +6,12 @@ module Projects
 
     DELETED_FLAG = '+deleted'
 
+    def pending_delete!
+      project.update_attribute(:pending_delete, true)
+
+      ProjectDestroyWorker.perform_in(1.minute, project.id, current_user.id, params)
+    end
+
     def execute
       return false unless can?(current_user, :remove_project, project)
 
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2015897dd198e6aceea4a4db4a4a9da263ebee68
--- /dev/null
+++ b/app/services/projects/import_service.rb
@@ -0,0 +1,67 @@
+module Projects
+  class ImportService < BaseService
+    include Gitlab::ShellAdapter
+
+    class Error < StandardError; end
+
+    ALLOWED_TYPES = [
+      'bitbucket',
+      'fogbugz',
+      'gitlab',
+      'github',
+      'google_code'
+    ]
+
+    def execute
+      if unknown_url?
+        # In this case, we only want to import issues, not a repository.
+        create_repository
+      else
+        import_repository
+      end
+
+      import_data
+
+      success
+    rescue Error => e
+      error(e.message)
+    end
+
+    private
+
+    def create_repository
+      unless project.create_repository
+        raise Error, 'The repository could not be created.'
+      end
+    end
+
+    def import_repository
+      begin
+        gitlab_shell.import_repository(project.path_with_namespace, project.import_url)
+      rescue Gitlab::Shell::Error => e
+        raise Error, e.message
+      end
+    end
+
+    def import_data
+      return unless has_importer?
+
+      unless importer.execute
+        raise Error, 'The remote data could not be imported.'
+      end
+    end
+
+    def has_importer?
+      ALLOWED_TYPES.include?(project.import_type)
+    end
+
+    def importer
+      class_name = "Gitlab::#{project.import_type.camelize}Import::Importer"
+      class_name.constantize.new(project)
+    end
+
+    def unknown_url?
+      project.import_url == Project::UNKNOWN_IMPORT_URL
+    end
+  end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 83f6814d822e23ebbe3eb0c65c561c79aa624fba..b0f1a34cbeca31e58e5524f216dd3733c379cccf 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.new)
     .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: PersonalSnippet)
+        = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new)
     .form-group
       = f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
       .col-sm-10
@@ -105,14 +105,14 @@
             = f.check_box :signin_enabled
             Sign-in enabled
     .form-group
-      = f.label :two_factor_authentication, 'Two-Factor authentication', class: 'control-label col-sm-2'
+      = 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
+            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'
+      = 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
@@ -226,11 +226,30 @@
         = 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
+          %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'
 
+  %fieldset
+    %legend Error Reporting and Logging
+    %p
+      These settings require a restart to take effect.
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .checkbox
+          = f.label :sentry_enabled do
+            = f.check_box :sentry_enabled
+            Enable Sentry
+          .help-block
+            Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here:
+            %a{ href: 'https://getsentry.com', target: '_blank' } https://getsentry.com
+
+    .form-group
+      = f.label :sentry_dsn, 'Sentry DSN', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_field :sentry_dsn, class: 'form-control'
+
   .form-actions
-    = f.submit 'Save', class: 'btn btn-primary'
+    = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml
index fa4e6335c735271e8daaae9287f07038aca9953f..e18f7b499dd3fc9543fb25a5362b0164c44c2259 100644
--- a/app/views/admin/applications/_form.html.haml
+++ b/app/views/admin/applications/_form.html.haml
@@ -22,5 +22,5 @@
           %code= Doorkeeper.configuration.native_redirect_uri
           for local tests
   .form-actions
-    = f.submit 'Submit', class: "btn btn-primary wide"
+    = f.submit 'Submit', class: "btn btn-save wide"
     = link_to "Cancel", admin_applications_path, class: "btn btn-default"
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 841e6971fb2dd84c27a8c4cd85faeff71b1d60d7..41c438999781c8c99428397f14b7183fa2cb3490 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -2,7 +2,7 @@
 .panel.panel-default
   .panel-heading
     Public deploy keys (#{@deploy_keys.count})
-    .panel-head-actions
+    .controls
       = link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm"
   - if @deploy_keys.any?
     .table-holder
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 8de2ba74a79bf77c85882613c7b9e98bfa5ac7ed..198026a1f7517673a5165a706dad84ae94f00c7c 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -21,6 +21,5 @@
 
   - else
     .form-actions
-      = f.submit 'Save changes', class: "btn btn-primary"
+      = f.submit 'Save changes', class: "btn btn-save"
       = link_to  'Cancel', admin_group_path(@group), class: "btn btn-cancel"
-
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 3940210e19b408fcc7aa1aa3b03b60d7fe13c83d..118d3cfea0742d0bd396c94d0e6b21b245d964f9 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -17,7 +17,7 @@
   .pull-right
     .dropdown.inline
       %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
-        %span.light sort:
+        %span.light
         - if @sort.present?
           = sort_options_hash[@sort]
         - else
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index b120f4dea67b675ea92c6d84a3405ac60eaa6b5f..53b3cd04c682964175ec96071a94b82a4d998f2a 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -37,8 +37,7 @@
       - @hooks.each do |hook|
         %li
           .list-item-name
-            = link_to admin_hook_path(hook) do
-              %strong= hook.url
+            %strong= hook.url
             %p SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
 
           .pull-right
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index d9b481404f75d1f9ae841d9a4341b72df08e255b..d39c0f440319b077143902042006a22158800f2f 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -1,7 +1,7 @@
 - page_title "Projects"
 = render 'shared/show_aside'
 
-.row
+.row.prepend-top-default
   %aside.col-md-3
     .admin-filter
       = form_tag admin_namespaces_projects_path, method: :get, class: '' do
@@ -47,10 +47,10 @@
     .panel.panel-default
       .panel-heading
         Projects (#{@projects.total_count})
-        .panel-head-actions
+        .controls
           .dropdown.inline
             %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
-              %span.light sort:
+              %span.light
               - if @sort.present?
                 = sort_options_hash[@sort]
               - else
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index b050a4d01c3312116ec4c560e7510396899d8ab0..b6b1168bd37798174d8e60b7a118ffe3489931c4 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -32,7 +32,7 @@
     .pull-right
       .dropdown.inline
         %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
-          %span.light sort:
+          %span.light
           - if @sort.present?
             = sort_options_hash[@sort]
           - else
diff --git a/app/views/dashboard/projects/index.atom.builder b/app/views/dashboard/projects/index.atom.builder
index 2e2712c5146e75110bc95575bb72c6c1b6c42752..d4daf07c6c0ee17bf18e191e167d62551eea9c95 100644
--- a/app/views/dashboard/projects/index.atom.builder
+++ b/app/views/dashboard/projects/index.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
   xml.link    href: dashboard_projects_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
   xml.link    href: dashboard_projects_url, rel: "alternate", type: "text/html"
   xml.id      dashboard_projects_url
-  xml.updated @events.latest_update_time.xmlschema if @events.any?
+  xml.updated @events[0].updated_at.xmlschema if @events[0]
 
   @events.each do |event|
     event_to_atom(xml, event)
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index 15f9ee266c15a257b806e3997708a42333001df4..eae80e5210fd2f29e2ef58b09ea558e076f99044 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -4,6 +4,15 @@
     Authorize
     %strong.text-info= @pre_auth.client.name
     to use your account?
+
+  - if current_user.admin?
+    .text-warning.prepend-top-20
+      %p
+        = icon("exclamation-triangle fw")
+        You are an admin, which means granting access to
+        %strong #{@pre_auth.client.name}
+        will allow them to interact with GitLab as an admin as well. Proceed with caution.
+
   - if @pre_auth.scopes
     #oauth-permissions
       %p This application will be able to:
@@ -25,4 +34,4 @@
       = hidden_field_tag :state, @pre_auth.state
       = hidden_field_tag :response_type, @pre_auth.response_type
       = hidden_field_tag :scope, @pre_auth.scope
-      = submit_tag "Deny", class: "btn btn-danger prepend-left-10"
\ No newline at end of file
+      = submit_tag "Deny", class: "btn btn-danger prepend-left-10"
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index fcb07b04083ae03470f5f608e267b66a74426484..8ffca96bb4eee95d644fedc150eee85c6152a830 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -18,7 +18,7 @@
   .pull-right
     .dropdown.inline
       %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
-        %span.light sort:
+        %span.light
         - if @sort.present?
           = sort_options_hash[@sort]
         - else
diff --git a/app/views/explore/projects/_dropdown.html.haml b/app/views/explore/projects/_dropdown.html.haml
index b23a3c1e5c170e5165627987c4ab6dfa5ae672d0..a988d4c815460017441653ec2299f0b8d23458bc 100644
--- a/app/views/explore/projects/_dropdown.html.haml
+++ b/app/views/explore/projects/_dropdown.html.haml
@@ -1,6 +1,6 @@
 .dropdown.inline
   %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
-    %span.light sort:
+    %span.light
     - if @sort.present?
       = sort_options_hash[@sort]
     - elsif current_page?(trending_explore_projects_path) || current_page?(explore_root_path)
@@ -24,4 +24,3 @@
         = sort_title_recently_updated
       = link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do
         = sort_title_oldest_updated
-
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index e2f97fd933756e15f8ac0f5d1e4f937cb6f2fac7..3430f56a9c98890681fba9d0974beab6ebe7d471 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,5 +1,4 @@
 - header_title group_title(@group, "Settings", edit_group_path(@group))
-- @blank_container = true
 
 .panel.panel-default.prepend-top-default
   .panel-heading
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 6a8acc42af9cac872ea76d8f0e9852aab62bab1f..6b7fd5746d6fb1461518985b5e79452c8550b984 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,6 +1,5 @@
 - page_title "Members"
 - header_title group_title(@group, "Members", group_group_members_path(@group))
-- @blank_container = true
 
 .group-members-page.prepend-top-default
   - if current_user && current_user.can?(:admin_group_member, @group)
@@ -20,7 +19,7 @@
       group members
       %small
         (#{@members.total_count})
-      .pull-right
+      .controls
         = 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 }
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 9ca11ed117738f97ea968a0e6d6aed393cbd6448..dd75766121ef2f390e06d5db39be7b64e1802a3a 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -6,9 +6,9 @@
     %strong= @group.name
     projects:
     - if can? current_user, :admin_group, @group
-      .panel-head-actions
+      .controls
         = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do
-          %i.fa.fa-plus
+          = icon('plus')
           New Project
   %ul.well-list
     - @projects.each do |project|
diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder
index 5cc0f5e1d2ede8423f88ce2acb6f70827a16260c..c66b82bb484f06b4099d7b51c3ae19b580e2bb92 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
   xml.link    href: group_url(@group, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
   xml.link    href: group_url(@group), rel: "alternate", type: "text/html"
   xml.id      group_url(@group)
-  xml.updated @events.latest_update_time.xmlschema if @events.any?
+  xml.updated @events[0].updated_at.xmlschema if @events[0]
 
   @events.each do |event|
     event_to_atom(xml, event)
diff --git a/app/views/kaminari/gitlab/_next_page.html.haml b/app/views/kaminari/gitlab/_next_page.html.haml
index 00c5f0b6f4e3fc9a71c81d2a674fcaac213c856d..c805914fc3fcc03033c27734eefa49c2173b711f 100644
--- a/app/views/kaminari/gitlab/_next_page.html.haml
+++ b/app/views/kaminari/gitlab/_next_page.html.haml
@@ -5,5 +5,9 @@
 -#    num_pages:     total number of pages
 -#    per_page:      number of items to fetch per page
 -#    remote:        data-remote
-%li.next
-  = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote
+- if current_page.last?
+  %li{ class: "next disabled" }
+    %span= raw(t 'views.pagination.next')
+- else
+  %li{ class: "next" }
+    = link_to raw(t 'views.pagination.next'), url, rel: 'next', remote: remote
diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml
index 2f645186921885198c412560842cf6eeffa00844..a12c53bcfe713401ebcf6d1e026f5edd59cf6917 100644
--- a/app/views/kaminari/gitlab/_paginator.html.haml
+++ b/app/views/kaminari/gitlab/_paginator.html.haml
@@ -10,13 +10,13 @@
     %ul.pagination.clearfix
       - unless current_page.first?
         = first_page_tag unless num_pages < 5 # As kaminari will always show the first 5 pages
-        = prev_page_tag
+      = prev_page_tag
       - each_page do |page|
         - if page.left_outer? || page.right_outer? || page.inside_window?
           = page_tag page
         - elsif !page.was_truncated?
           = gap_tag
+      = next_page_tag
       - unless current_page.last?
-        = next_page_tag
         = last_page_tag unless num_pages < 5
 
diff --git a/app/views/kaminari/gitlab/_prev_page.html.haml b/app/views/kaminari/gitlab/_prev_page.html.haml
index f673abdb3ae8b6cc9859f1ede9d24a44e431c551..afb20455e0a3727f2fb70f7fed0b489a9af03f43 100644
--- a/app/views/kaminari/gitlab/_prev_page.html.haml
+++ b/app/views/kaminari/gitlab/_prev_page.html.haml
@@ -5,5 +5,9 @@
 -#    num_pages:     total number of pages
 -#    per_page:      number of items to fetch per page
 -#    remote:        data-remote
-%li{class: "prev" }
-  = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote
+- if current_page.first?
+  %li{ class: "prev disabled" }
+    %span= raw(t 'views.pagination.previous')
+- else
+  %li{ class: "prev" }
+    = link_to raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 3892ef8eefa026bb8f2332a0ff99f074d620db85..fcb6b835a7e07ebdc0036a7d455f15179f44d6f6 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -37,3 +37,6 @@
       %h1.title= title
 
 = render 'shared/outdated_browser'
+- if @project && !@project.empty_repo?
+  :javascript
+    var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, @ref || @project.repository.root_ref)}";
\ No newline at end of file
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 270ccfd387f8f26693e1aa2a23bde4d5a35470aa..319974e12c5388d0e41d15d02cee9c6e12dc3a8c 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -98,6 +98,13 @@
         %span
           Wiki
 
+  - if project_nav_tab? :forks
+    = nav_link(controller: :forks, action: :index) do
+      = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks' do
+        = icon('code-fork fw')
+        %span
+          Forks
+
   - if project_nav_tab? :snippets
     = nav_link(controller: :snippets) do
       = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 3dd2595f1ad6ff67f512fcde8e006d9393f86ab3..f2e405b14fd30c0dc25b79428b9077770e784f1c 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -18,7 +18,7 @@
         %div
           %span by #{commit.author_name}
           %i at #{commit.committed_date.to_s(:iso8601)}
-        %pre.commit-message 
+        %pre.commit-message
           = commit.safe_message
 
   %h4 #{pluralize @message.diffs_count, "changed file"}:
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index a42fd38de3a91b6a458318c2059bf537871a0a0a..52bfc595fda46263d599fe1152798899c67eaecd 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,6 +1,5 @@
 - page_title "Account"
 - header_title page_title, profile_account_path
-- @blank_container = true
 
 - if current_user.ldap_user?
   .alert.alert-info
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index 1a5b6efce3558e457a129203bff3fad0589f28a2..b2830aa08343cf0dfd40260e141b05c3f3a76227 100644
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -1,6 +1,6 @@
 - page_title 'Two-factor Authentication', 'Account'
 
-%h2.page-title Two-Factor Authentication (2FA)
+%h2.page-title Two-factor Authentication (2FA)
 %p
   Download the Google Authenticator application from App Store for iOS or Google
   Play for Android and scan this code.
diff --git a/app/views/projects/artifacts/_tree_directory.html.haml b/app/views/projects/artifacts/_tree_directory.html.haml
index e4b7979949cbf153d8e41fe3b1dc5e8ba8284235..def493c56f5148000522770ece1ced9dcf76368f 100644
--- a/app/views/projects/artifacts/_tree_directory.html.haml
+++ b/app/views/projects/artifacts/_tree_directory.html.haml
@@ -6,4 +6,3 @@
     %span.str-truncated
       = link_to directory.name, path_to_directory
   %td
-  %td
diff --git a/app/views/projects/artifacts/_tree_file.html.haml b/app/views/projects/artifacts/_tree_file.html.haml
index 3dfc09cc495b5abffcf9621033f53f70bfcde16b..36fb4c998c96bac066dba1a43dbcc94b968b7379 100644
--- a/app/views/projects/artifacts/_tree_file.html.haml
+++ b/app/views/projects/artifacts/_tree_file.html.haml
@@ -7,5 +7,3 @@
       = link_to file.name, path_to_file
   %td
     = number_to_human_size(file.metadata[:size], precision: 2)
-  %td
-    = number_to_human_size(file.metadata[:zipped], precision: 2)
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index b70c776a2b21da983190153ed941b7cbeb8741f8..84034c8bf16e952e173235f1b991950014d7e97e 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -4,7 +4,7 @@
 .top-block.gray-content-block.clearfix
   .pull-right
     = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build),
-      class: 'btn btn-default' do
+      class: 'btn btn-default download' do
       = icon('download')
       Download artifacts archive
 
@@ -15,18 +15,8 @@
         %tr
           %th Name
           %th Size
-          %th Compressed to
       = render partial: 'tree_directory', collection: @entry.directories(parent: true), as: :directory
       = render partial: 'tree_file', collection: @entry.files, as: :file
 
 - if @entry.empty?
   .center Empty
-
-:javascript
-  $('.tree-holder').on('click', 'tr[data-link] a', function(e) {
-    e.stopImmediatePropagation();
-  });
-
-  $('.tree-holder').on('click', 'tr[data-link]', function(e) {
-    window.location = this.dataset.link;
-  });
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 8d9ec068a4358a38ba030fb9665033a67d9ea643..eb6fbfaffa0eea9bc2033ca9a2c42ebc59546a7e 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -12,14 +12,14 @@
       %small= number_to_human_size @blob.size
       .file-actions
         = render "projects/blob/actions"
-    .file-content.blame.highlight
+    .file-content.blame.code.js-syntax-highlight
       %table
         - current_line = 1
-        - @blame.each do |blame_group|
+        - @blame_groups.each do |blame_group|
           %tr
             %td.blame-commit
               .commit
-                - commit = Commit.new(blame_group[:commit], @project)
+                - commit = blame_group[:commit]
                 .commit-row-title
                   %strong
                     = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
@@ -30,16 +30,14 @@
                   = commit_author_link(commit, avatar: false)
                   authored
                   #{time_ago_with_tooltip(commit.committed_date, skip_js: true)}
-            %td.lines.blame-numbers
-              %pre
-                - line_count = blame_group[:lines].count
-                - (current_line...(current_line + line_count)).each do |i|
-                  = i
-                  \
-                - current_line += line_count
+            %td.line-numbers
+              - line_count = blame_group[:lines].count
+              - (current_line...(current_line + line_count)).each do |i|
+                %a.diff-line-num= i
+                \
+              - current_line += line_count
             %td.lines
-              %pre{class: 'code highlight white'}
+              %pre.code.highlight
                 %code
                   - blame_group[:lines].each do |line|
-                    :erb
-                      <%= highlight(@blob.name, line, nowrap: true, continue: true).html_safe %>
+                    #{line}
diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index 4429c395aee79de4cc0f37e54457149dc9ab2852..906e5ccb360c106eb03d04020eeec24262219781 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -2,8 +2,8 @@
   .file-content.wiki
     = render_markup(blob.name, blob.data)
 - else
-  .file-content.code
-    - unless blob.empty?
-      = render 'shared/file_highlight', blob: blob
-    - else
+  - unless blob.empty?
+    = render 'shared/file_highlight', blob: blob
+  - else
+    .file-content.code
       .nothing-here-block Empty file
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index f3b01ff3288b1723eecaa8f266eae7111746514c..abcfca4cd11bf876978ca7e8023502c5534b39e5 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -10,8 +10,9 @@
     %tr.line_holder
       %td.old_line.diff-line-num{data: {linenumber: line_old}}
         = link_to raw(line_old), "#"
-      %td.new_line= link_to raw(line_new) , "#"
-      %td.line_content.noteable_line= ' ' * @form.indent + line
+      %td.new_line.diff-line-num
+        = link_to raw(line_new) , "#"
+      %td.line_content.noteable_line==#{' ' * @form.indent}#{line}
 
   - if @form.unfold? && @form.bottom? && @form.to < @blob.loc
     %tr.line_holder{ id: @form.to }
diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml
index e7c3460ad7852ec49bfe84f0e0af009ec2634bd3..541dc96c45f949ac167be508f98f65c2a6d49f06 100644
--- a/app/views/projects/blob/preview.html.haml
+++ b/app/views/projects/blob/preview.html.haml
@@ -8,18 +8,18 @@
       .file-content.wiki
         = raw render_markup(@blob.name, @content)
     - else
-      .file-content.code
+      .file-content.code.js-syntax-highlight
         - unless @diff_lines.empty?
           %table.text-file
             - @diff_lines.each do |line|
               %tr.line_holder{ class: "#{line.type}" }
                 - if line.type == "match"
-                  %td.old_line= "..."
-                  %td.new_line= "..."
-                  %td.line_content.matched= line.text
+                  %td.old_line.diff-line-num= "..."
+                  %td.new_line.diff-line-num= "..."
+                  %td.line_content.match= line.text
                 - else
-                  %td.old_line
-                  %td.new_line
-                  %td.line_content{class: "#{line.type}"}= raw diff_line_content(line.text)
+                  %td.old_line.diff-line-num
+                  %td.new_line.diff-line-num
+                  %td.line_content{class: "#{line.type}"}= diff_line_content(line.text)
         - else
           .nothing-here-block No changes.
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 204def6079426ffd6933849260ebcce558405a9e..7afea5a5049cd2ac7f5b5a18547ea1fb42aef956 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -10,7 +10,7 @@
       &nbsp;
     .dropdown.inline
       %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
-        %span.light sort:
+        %span.light
         - if @sort.present?
           = @sort.humanize
         - else
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 2be572d3b10edfe9f690066c7d6add3fca753f0a..ba1fdc6f0e7416ea88419de2784facb9baf96e73 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -96,7 +96,7 @@
           .center
             .btn-group{ role: :group }
               = link_to "Download", @build.artifacts_download_url, class: 'btn btn-sm btn-primary'
-              - if @build.artifacts_browser_supported?
+              - if @build.artifacts_metadata?
                 = link_to "Browse", @build.artifacts_browse_url, class: 'btn btn-sm btn-primary'
 
       .build-widget
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 511863d774e82bcd754ff21c95fccf43d813c8a3..e7c85edff9673d063318bbe86053b4e0d7100488 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -46,7 +46,7 @@
           - 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,
+          - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key:  current_user.namespace.id,
                                                                                   continue:       continue_params)
           = link_to fork_path, method: :post do
             = icon('file fw')
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 02297158dec5964333e0e1e6ee09e5a18070fa41..05dbe5ebea41292ccdad88a99c81ce54931b7b47 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -9,5 +9,6 @@
   = render "ci_menu"
 - else
   %div.block-connector
-= render "projects/diffs/diffs", diffs: @diffs, project: @project
+= render "projects/diffs/diffs", diffs: @diffs, project: @project,
+         diff_refs: @diff_refs
 = 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 1736dccaf3c755c3614345159c95381e4f454614..2e3c956ddc44b7aa8665cd4dd19ee02a87c6c3c3 100644
--- a/app/views/projects/commit_statuses/_commit_status.html.haml
+++ b/app/views/projects/commit_statuses/_commit_status.html.haml
@@ -66,7 +66,7 @@
 
   %td
     .pull-right
-      - if current_user && can?(current_user, :read_build_artifacts, commit_status.project) && commit_status.artifacts?
+      - if current_user && can?(current_user, :read_build_artifacts, commit_status.project) && commit_status.artifacts_download_url
         = link_to commit_status.artifacts_download_url, title: 'Download artifacts' do
           %i.fa.fa-download
       - if current_user && can?(current_user, :manage_builds, commit_status.project)
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 51088a7dea89b0e97622dde7a1b0ba641ea8c0d8..da731f28bb608109b7b221601e9069a0c4606d39 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -9,7 +9,7 @@
 - if @commits.present?
   .prepend-top-default
     = render "projects/commits/commit_list"
-    = render "projects/diffs/diffs", diffs: @diffs, project: @project
+    = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @diff_refs
 - else
   .light-well.prepend-top-default
     .center
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index f67058ae0ba86e0b52de2fda81316ac276e37bb4..d668f483bcb3e40fec09247d8c07c4896949e275 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -1,7 +1,7 @@
 - if diff_view == 'parallel'
   - fluid_layout true
 
-- diff_files = safe_diff_files(diffs)
+- diff_files = safe_diff_files(diffs, diff_refs)
 
 .content-block.oneline-block
   .inline-parallel-buttons
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 517f6aef7c5a89e356407058e781455467cb8640..3ac058a3bf8c1b9a2b9088946adb1b4e4f021818 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,39 +1,41 @@
-.diff-file{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)}
-  .diff-header{id: "file-path-#{hexdigest(diff_file.file_path)}"}
+.diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)}
+  .file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"}
     - if diff_file.diff.submodule?
       %span
         = 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
+      = blob_icon blob.mode, blob.name
 
-        - if diff_file.deleted_file
-          deleted
-        - elsif diff_file.renamed_file
-          renamed from
+      = link_to "#diff-#{i}" do
+        - if diff_file.renamed_file
+          - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
+          %strong.filename.old
+            = old_path
+          &rarr;
+          %strong.filename.new
+            = new_path
+        - else
           %strong
-            = diff_file.old_path
+            = diff_file.new_path
+          - if diff_file.deleted_file
+            deleted
 
-        - if diff_file.mode_changed?
-          %small
-            = "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
+      - if diff_file.mode_changed?
+        %small
+          = "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
 
-      .diff-controls
+      .file-actions.hidden-xs
         - 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;
+          = link_to '#', class: 'js-toggle-diff-comments btn active has_tooltip', title: "Toggle comments for this file" do
+            = icon('comments')
+          \
 
         - if editable_diff?(diff_file)
           = edit_blob_link(@merge_request.source_project,
               @merge_request.source_branch, diff_file.new_path,
               from_merge_request_id: @merge_request.id)
-          &nbsp;
 
         = view_file_btn(diff_commit.id, diff_file, project)
 
diff --git a/app/views/projects/diffs/_match_line.html.haml b/app/views/projects/diffs/_match_line.html.haml
index d1f897b99f78680591956eb00f967bf21ef9a90b..d6dddd97879ce85f9f53667eaa91e7055ff1a0f4 100644
--- a/app/views/projects/diffs/_match_line.html.haml
+++ b/app/views/projects/diffs/_match_line.html.haml
@@ -4,4 +4,4 @@
 %td.new_line.diff-line-num{data: {linenumber: line_new},
  class: [unfold_bottom_class(bottom), unfold_class(!new_file)]}
   \...
-%td.line_content.matched= line
+%td.line_content.match= line
diff --git a/app/views/projects/diffs/_match_line_parallel.html.haml b/app/views/projects/diffs/_match_line_parallel.html.haml
index 815df16aa4ab92ed646ae99df498d78fed75a5b0..0cd888876e02bc4ea7cf0f76fb5006353072af06 100644
--- a/app/views/projects/diffs/_match_line_parallel.html.haml
+++ b/app/views/projects/diffs/_match_line_parallel.html.haml
@@ -1,4 +1,4 @@
-%td.old_line
-  %td.line_content.parallel.matched= line
-%td.new_line
-  %td.line_content.parallel.matched= line
+%td.old_line.diff-line-num
+%td.line_content.parallel.match= line
+%td.new_line.diff-line-num
+%td.line_content.parallel.match= line
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 37fd1b1ec8a34d4ce6a276f4e38e681af01164a0..d7c490687452048974bb951e4f9232676644a70e 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -1,42 +1,40 @@
 / Side-by-side diff view
-%div.text-file.diff-wrap-lines
+%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight
   %table
-    - parallel_diff(diff_file, index).each do |line|
-      - type_left = line[0]
-      - line_number_left = line[1]
-      - line_content_left = line[2]
-      - line_code_left = line[3]
-      - type_right = line[4]
-      - line_number_right = line[5]
-      - line_content_right = line[6]
-      - line_code_right = line[7]
-
+    - diff_file.parallel_diff_lines.each do |line|
+      - left = line[:left]
+      - right = line[:right]
       %tr.line_holder.parallel
-        - if type_left == 'match'
-          = render "projects/diffs/match_line_parallel", { line: line_content_left,
-          line_old: line_number_left, line_new: line_number_right }
-        - elsif type_left ==  'old' || type_left.nil?
-          %td.old_line{id: line_code_left, class: "#{type_left}"}
-            = link_to raw(line_number_left), "##{line_code_left}", id: line_code_left
+        - if left[:type] == 'match'
+          = render "projects/diffs/match_line_parallel", { line: left[:text],
+          line_old: left[:number], line_new: right[:number] }
+        - elsif left[:type] == 'nonewline'
+          %td.old_line.diff-line-num
+          %td.line_content.parallel.match= left[:text]
+          %td.new_line.diff-line-num
+          %td.line_content.parallel.match= left[:text]
+        - else
+          %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]}"}
+            = link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code]
             - if @comments_allowed && can?(current_user, :create_note, @project)
-              = link_to_new_diff_note(line_code_left, 'old')
-            %td.line_content{class: "parallel noteable_line #{type_left} #{line_code_left}", "line_code" => line_code_left }= raw line_content_left
+              = link_to_new_diff_note(left[:line_code], 'old')
+          %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
 
-          - if type_right == 'new'
+          - if right[:type] == 'new'
             - new_line_class = 'new'
-            - new_line_code = line_code_right
+            - new_line_code = right[:line_code]
           - else
             - new_line_class = nil
-            - new_line_code = line_code_left
+            - new_line_code = left[:line_code]
 
-          %td.new_line{id: new_line_code, class: "#{new_line_class}", data: { linenumber: line_number_right }}
-            = link_to raw(line_number_right), "##{new_line_code}", id: new_line_code
+          %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class}", data: { linenumber: right[:number] }}
+            = link_to raw(right[:number]), "##{new_line_code}", id: new_line_code
             - if @comments_allowed && can?(current_user, :create_note, @project)
-              = link_to_new_diff_note(line_code_right, 'new')
-            %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", "line_code" => new_line_code}= raw line_content_right
+              = link_to_new_diff_note(right[:line_code], 'new')
+          %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
 
       - if @reply_allowed
-        - comments_left, comments_right = organize_comments(type_left, type_right, line_code_left, line_code_right)
+        - comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code])
         - if comments_left.present? || comments_right.present?
           = render "projects/notes/diff_notes_with_reply_parallel", notes_left: comments_left, notes_right: comments_right
 
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 977ca423f75323cd1b13ac18c5307a7e64cca777..5e835b10e1fe5b70500581b71fda115bc2b3cff6 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -3,9 +3,11 @@
   .suppressed-container
     %a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show.
 
-%table.text-file{class: "#{'hide' if too_big}"}
+%table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' }
+
   - last_line = 0
-  - diff_file.diff_lines.each_with_index do |line, index|
+  - raw_diff_lines = diff_file.diff_lines
+  - diff_file.highlighted_diff_lines.each_with_index do |line, index|
     - type = line.type
     - last_line = line.new_pos
     - line_code = generate_line_code(diff_file.file_path, line)
@@ -14,19 +16,23 @@
       - if type == "match"
         = render "projects/diffs/match_line", {line: line.text,
           line_old: line_old, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file}
+      - elsif type == 'nonewline'
+        %td.old_line.diff-line-num
+        %td.new_line.diff-line-num
+        %td.line_content.match= line.text
       - else
-        %td.old_line
+        %td.old_line.diff-line-num{class: type}
           = link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
           - if @comments_allowed && can?(current_user, :create_note, @project)
             = link_to_new_diff_note(line_code)
-        %td.new_line{data: {linenumber: line.new_pos}}
-          = link_to raw(type == "old" ? "&nbsp;" : line.new_pos) , "##{line_code}", id: line_code
-        %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text)
+        %td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}}
+          = link_to raw(type == "old" ? "&nbsp;" : line.new_pos), "##{line_code}", id: line_code
+        %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text)
 
     - if @reply_allowed
       - comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at)
       - unless comments.empty?
-        = render "projects/notes/diff_notes_with_reply", notes: comments, line: line.text
+        = render "projects/notes/diff_notes_with_reply", notes: comments, line: raw_diff_lines[index].text
 
   - if last_line > 0
     = render "projects/diffs/match_line", {line: "",
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
index 40a2a61d8a1557ac1a8d41b173bbb23512636862..905f6bbbd48f7684f447cbcc402c22250fadb1aa 100644
--- a/app/views/projects/find_file/show.html.haml
+++ b/app/views/projects/find_file/show.html.haml
@@ -10,7 +10,7 @@
         = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
           = @project.path
       %li.file-finder
-        %input#file_find.form-control.file-finder-input{type: "text", placeholder: 'Find by path'}
+        %input#file_find.form-control.file-finder-input{type: "text", placeholder: 'Find by path', autocomplete: 'off'}
 
   %div.tree-content-holder
     .table-holder
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a362185210a6cc4bd03c663b926c0dac903932a9
--- /dev/null
+++ b/app/views/projects/forks/index.html.haml
@@ -0,0 +1,58 @@
+.gray-content-block.top-block.clearfix.white.forks-top-block
+  .pull-left
+    - public_count = @public_forks.size
+    - protected_count = @protected_forks.size
+    - full_count_title = "#{public_count} public and #{protected_count} private"
+    == #{pluralize(@all_forks.size, 'fork')}: #{full_count_title}
+
+  .pull-right
+    .projects-search-form.fork-search-form
+      = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter form-control',
+        spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
+
+      .dropdown.inline.prepend-left-10
+        %button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'}
+          %span.light sort:
+          - if @sort.present?
+            = sort_options_hash[@sort]
+          - else
+            = sort_title_recently_created
+          %b.caret
+        %ul.dropdown-menu.dropdown-menu-align-right
+          %li
+            - excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id]
+            = link_to page_filter_path(sort: sort_value_recently_created, without: excluded_filters) do
+              = sort_title_recently_created
+            = link_to page_filter_path(sort: sort_value_oldest_created, without: excluded_filters) do
+              = sort_title_oldest_created
+            = link_to page_filter_path(sort: sort_value_recently_updated, without: excluded_filters) do
+              = sort_title_recently_updated
+            = link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do
+              = sort_title_oldest_updated
+
+      .fork-link.inline
+        - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
+          = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'pull-right btn btn-new' do
+            = icon('code-fork fw')
+            Fork
+        - else
+          = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'pull-right btn btn-new' do
+            = icon('code-fork fw')
+            Fork
+
+
+.projects-list-holder
+  - if @public_forks.blank?
+    %ul.content-list
+      %li
+        .nothing-here-block No forks to show
+  - else
+    = render 'shared/projects/list', projects: @public_forks, use_creator_avatar: true,
+      forks: true, show_last_commit_as_description: true
+
+    - if protected_count > 0
+      %ul.projects-list.private-forks-notice
+        %li.project-row
+          = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon')
+          %strong= pluralize(protected_count, 'private fork')
+          %span you have no access to.
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index 8a2c027a45517d6ad288122d5b416c840f9ac006..edabc2d3b44e9bed2d4f982b412ddbad54026ecd 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -22,7 +22,7 @@
 
             - else
               .fork-thumbnail
-                = link_to namespace_project_fork_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
+                = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
                   = image_tag namespace_icon(namespace, 100)
                   .caption
                     %strong
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index e0e89b764d59eaf9d93adaf5c8a08625049636c5..f34f3c0573743d4cb746e3e51399c6d1054907bb 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -5,9 +5,4 @@
       .nothing-here-block No issues to show
 
 - if @issues.present?
-  .issuable-filter-count
-    %span.pull-right
-      = number_with_delimiter(@issues.total_count)
-      issues for this filter
-
   = paginate @issues, theme: "gitlab"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 7ed898ce72fbc6f1997baa09b4a91a70a8de0225..51dcca7a1abb5527b16f159d222a691dec423673 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -35,7 +35,7 @@
           Edit
 
   .issue-details.issuable-details
-    .detail-page-description.gray-content-block.second-block
+    .detail-page-description.content-block
       %h2.title
         = markdown escape_once(@issue.title), pipeline: :single_line
       %div
@@ -50,7 +50,7 @@
         .merge-requests
           = render 'merge_requests'
 
-    .gray-content-block.second-block.oneline-block
+    .content-block
       = render 'votes/votes_block', votable: @issue
 
     .row
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
index 29d09d0a6521de10e026924c0ebbf8d0fee40507..5473fa191662af30e0ecc02be3652e041e453e4f 100644
--- a/app/views/projects/merge_requests/_merge_requests.html.haml
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -5,10 +5,5 @@
       .nothing-here-block No merge requests to show
 
 - if @merge_requests.present?
-  .issuable-filter-count
-    %span.pull-right
-      = number_with_delimiter(@merge_requests.total_count)
-      merge requests for this filter
-
   = paginate @merge_requests, theme: "gitlab"
 
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index dd2c59e112a63eec5a73017843e6ce6939c09ceb..4c5a9818e3e636ee976600f88561b8f200916766 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -38,7 +38,7 @@
       = render "projects/merge_requests/show/commits"
     #diffs.diffs.tab-pane.active
       - if @diffs.present?
-        = render "projects/diffs/diffs", diffs: @diffs, project: @project
+        = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs
       - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
         .alert.alert-danger
           %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 200bfa5ac4fb48b6fe96c5cb19a2065d3a3e2b24..8641c3d8b4bf78c432915fb721bfc33dbc8fa00a 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -66,7 +66,7 @@
 
       .tab-content
         #notes.notes.tab-pane.voting_notes
-          .gray-content-block.second-block.oneline-block
+          .content-block.oneline-block
             = render 'votes/votes_block', votable: @merge_request
 
           .row
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index 7f904ec42a096e015c4e80d0522015272279f6a9..a8f09f855d48d41c57c67ca639ea87432ad21d6c 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.middle-block.oneline-block
+.content-block.oneline-block
   = icon("sort-amount-desc")
   Most recent commits displayed first
 
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
index d9cfc3d7ae943ba9df5f8afa7140a76e1463ec17..64cd484193e810d39f4774d45d18926f1eb341ae 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -1,5 +1,6 @@
 - if @merge_request_diff.collected?
-  = render "projects/diffs/diffs", diffs: params[:w] == '1' ? @merge_request.diffs_no_whitespace : @merge_request.diffs, project: @merge_request.project
+  = render "projects/diffs/diffs", diffs: params[:w] == '1' ? @merge_request.diffs_no_whitespace : @merge_request.diffs,
+    project: @merge_request.project, diff_refs: @merge_request.diff_refs
 - elsif @merge_request_diff.empty?
   .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
 - else
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 877cc3d744b9bf6855d52efd241a78691e0a5139..0dbd159298e8761b50acc8e52396df6fcf76a88f 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
@@ -45,6 +45,10 @@
         - unless @merge_request.can_be_merged_by?(current_user)
           %p
             Note that pushing to GitLab requires write access to this repository.
+        %p
+          %strong Tip:
+          You can also checkout merge requests locally by
+          %a{href: 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/workflow/merge_requests.md#checkout-merge-requests-locally', target: '_blank'} following these guidelines
 
 :javascript
   $(function(){
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 0f81e5e891424ff42f77c0011efc7e87cc79288c..905823f79d9391526825905fc2f3ac0cb9992a0c 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,4 +1,4 @@
-.detail-page-description.gray-content-block.second-block
+.detail-page-description.content-block
   %h2.title
     = markdown escape_once(@merge_request.title), pipeline: :single_line
 
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 1142c58459292ed61921357c802f2428fad2199e..528a4f9552f7ae15020ec00572b0af5e96bcc516 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -32,7 +32,7 @@
         = icon('pencil-square-o')
         Edit
 
-.detail-page-description.gray-content-block.second-block
+.detail-page-description.content-block
   %h2.title
     = markdown escape_once(@milestone.title), pipeline: :single_line
   %div
@@ -73,8 +73,8 @@
 
 .tab-content
   .tab-pane.active#tab-issues
-    .gray-content-block.middle-block
-      .pull-right
+    .content-block.oneline-block
+      .controls
         - 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
@@ -94,8 +94,8 @@
         = render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
 
   .tab-pane#tab-merge-requests
-    .gray-content-block.middle-block
-      .pull-right
+    .content-block.oneline-block
+      .controls
         - 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"
 
@@ -117,9 +117,8 @@
               = render 'merge_request', merge_request: merge_request
 
   .tab-pane#tab-participants
-    .gray-content-block.middle-block
-      .oneline
-        All participants to this milestone
+    .content-block.oneline-block
+      All participants to this milestone
 
     %ul.bordered-list
       - @users.each do |user|
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
index c731baf0a651e225be51be50b74787999a2eb3f1..11f9859a90f4e7c4004d9cd77646784ab9f49d48 100644
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -7,7 +7,7 @@
         %i.fa.fa-comment
         = notes.count
     %td.notes_content
-      %ul.notes{ rel: note.discussion_id }
+      %ul.notes{ data: { discussion_id: note.discussion_id } }
         = render notes
       .discussion-reply-holder
         = link_to_reply_diff(note)
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
index c6726cbafa3eb5941bed705e0873c83386e312e8..bb761ed2f946b0dd21438077b86253a1ab8ddbaa 100644
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -8,7 +8,7 @@
         %i.fa.fa-comment
         = notes_left.count
     %td.notes_content.parallel.old
-      %ul.notes{ rel: note1.discussion_id }
+      %ul.notes{ data: { discussion_id: note1.discussion_id } }
         = render notes_left
 
       .discussion-reply-holder
@@ -23,7 +23,7 @@
         %i.fa.fa-comment
         = notes_right.count
     %td.notes_content.parallel.new
-      %ul.notes{ rel: note2.discussion_id }
+      %ul.notes{ data: { discussion_id: note2.discussion_id } }
         = render notes_right
 
       .discussion-reply-holder
@@ -31,4 +31,3 @@
   - else
     %td.notes_line.new= ""
     %td.notes_content.parallel.new= ""
-
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 922535e5c4a67fbc22a94684d3c24ef45bdee9e6..e858c412836075137a5469299b6a577b70c2350e 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,4 +1,4 @@
-%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } }
+%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)] }
   .timeline-entry-inner
     .timeline-icon
       %a{href: user_path(note.author)}
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index eb378b4260367a15996999c525430642ae2660fa..910eb6cf66ec871baa89b080abc4116eedf6a899 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -5,6 +5,16 @@
 .js-main-target-form
 - if can? current_user, :create_note, @project
   = render "projects/notes/form", view: diff_view
+- else
+  .disabled-comment-area
+    .disabled-profile
+    .disabled-comment
+      %span
+        Please
+        = link_to "register",new_user_session_path
+        or
+        = link_to "login",new_user_session_path
+        to post a comment
 
 :javascript
   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/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
index 6903fad4a0aa53ec20be59613334e6746a8561af..3da2f2060b8845de60243c7b01bb09c52315c948 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -20,8 +20,7 @@
       = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
     - else
       .panel.panel-default
-        .notes{ rel: discussion_notes.first.discussion_id }
+        .notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
           = render discussion_notes
         .discussion-reply-holder
           = link_to_reply_diff(discussion_notes.first)
-
diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml
index 0301445b5b28f02c18eeb456acde6e2dba3bdc96..820e31ccd61b8528cdc4f2388688918572f46bd8 100644
--- a/app/views/projects/notes/discussions/_diff.html.haml
+++ b/app/views/projects/notes/discussions/_diff.html.haml
@@ -9,22 +9,22 @@
           = diff.new_path
           - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
             %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
-    .diff-content
+    .diff-content.code.js-syntax-highlight
       %table
         - note.truncated_diff_lines.each do |line|
           - type = line.type
           - line_code = generate_line_code(note.file_path, line)
           %tr.line_holder{ id: line_code, class: "#{type}" }
             - if type == "match"
-              %td.old_line= "..."
-              %td.new_line= "..."
-              %td.line_content.matched= line.text
+              %td.old_line.diff-line-num= "..."
+              %td.new_line.diff-line-num= "..."
+              %td.line_content.match= line.text
             - else
-              %td.old_line
+              %td.old_line.diff-line-num
                 = raw(type == "new" ? "&nbsp;" : line.old_pos)
-              %td.new_line
+              %td.new_line.diff-line-num
                 = raw(type == "old" ? "&nbsp;" : line.new_pos)
-              %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text)
+              %td.line_content{class: "noteable_line #{type} #{line_code}", line_code: line_code}= diff_line_content(line.text)
 
               - if line_code == note.line_code
                 = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index 1c2458fa1440aa29cae51edb46d7a02977240f7b..c53033e367c8f2f743764f1323b92487f511ade5 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -5,7 +5,7 @@
     %small
       (#{members.count})
     - if can?(current_user, :admin_group_member, @group)
-      .pull-right
+      .controls
         = link_to group_group_members_path(@group), class: 'btn' do
           = icon('pencil-square-o')
           Manage group members
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index ccddab13aafb82e5f6d932be48d9f6525158a8fa..e8dce30425f13d48bad944618d5abb6046fb626e 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -4,7 +4,7 @@
     project members
     %small
       (#{members.count})
-    .pull-right
+    .controls
       = 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 }
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 6239a148905d2c761f28fd1ba003fffe742ac9e6..0f8848a5cbe644096a183e10e4cc4e98f64a2cda 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,13 +1,12 @@
 - page_title "Members"
 = render "header_title"
-- @blank_container = true
 
 .project-members-page.prepend-top-default
   - if can?(current_user, :admin_project_member, @project)
     .panel.panel-default
       .panel-heading
         Add new user to project
-        .pull-right
+        .controls
           = 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
diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder
index d6762219108229d8aa06e148c4aba4263309ce8f..2468509242afdd7f3a6190f4292c02035cca9c38 100644
--- a/app/views/projects/show.atom.builder
+++ b/app/views/projects/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
   xml.link    href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
   xml.link    href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html"
   xml.id      namespace_project_url(@project.namespace, @project)
-  xml.updated @events.latest_update_time.xmlschema if @events.any?
+  xml.updated @events[0].updated_at.xmlschema if @events[0?
 
   @events.each do |event|
     event_to_atom(xml, event)
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 3343288ad2b1da800a7ce50eca1176ea2c19ce9d..3eb626e6dcac84f3ef86c54125e85e01527e2e2a 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -40,7 +40,7 @@
                 - 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,
+                - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key:  current_user.namespace.id,
                                                                                         continue:       continue_params)
                 = link_to fork_path, method: :post do
                   = icon('pencil fw')
@@ -49,7 +49,7 @@
                 - 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,
+                - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key:  current_user.namespace.id,
                                                                                         continue:       continue_params)
                 = link_to fork_path, method: :post do
                   = icon('file fw')
@@ -58,7 +58,7 @@
                 - 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,
+                - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key:  current_user.namespace.id,
                                                                                         continue:       continue_params)
                 = link_to fork_path, method: :post do
                   = icon('folder fw')
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 2efa616d6644e453a53415d6fcf8fbdac5eb4c33..faeb2b55c6f7a6d3791053f858b725c2a8f06983 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -6,7 +6,7 @@
   - if merge_request.description.present?
     .description.term
       = preserve do
-        = search_md_sanitize(markdown(merge_request.description))
+        = search_md_sanitize(markdown(merge_request.description, { project: merge_request.project }))
   %span.light
     #{merge_request.project.name_with_namespace}
   .pull-right
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index 9a4f9fb9485433c382bfdce23f6ea87be3572f78..dcd6119971736c27d651482c2f218b999c2af8c2 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -22,29 +22,27 @@
               .file-content.code
                 .nothing-here-block Empty file
       - else
-        .file-content.code
-          %div.highlighted-data{ class: user_color_scheme }
-            .line-numbers
+        .file-content.code.js-syntax-highlight
+          .line-numbers
+            - snippet_blob[:snippet_chunks].each do |snippet|
+              - unless snippet[:data].empty?
+                - snippet[:data].lines.to_a.size.times do |index|
+                  - offset = defined?(snippet[:start_line]) ? snippet[:start_line] : 1
+                  - i = index + offset
+                  = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do
+                    %i.fa.fa-link
+                    = i
+                - unless snippet == snippet_blob[:snippet_chunks].last
+                  %a.diff-line-num
+                    = "."
+          %pre.code
+            %code
               - snippet_blob[:snippet_chunks].each do |snippet|
                 - unless snippet[:data].empty?
-                  - snippet[:data].lines.to_a.size.times do |index|
-                    - offset = defined?(snippet[:start_line]) ? snippet[:start_line] : 1
-                    - i = index + offset
-                    = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}" do
-                      %i.fa.fa-link
-                      = i
+                  = snippet[:data]
                   - unless snippet == snippet_blob[:snippet_chunks].last
                     %a
-                      = "."
-            .highlight.term
-              %pre
-                %code
-                  - snippet_blob[:snippet_chunks].each do |snippet|
-                    - unless snippet[:data].empty?
-                      = snippet[:data]
-                      - unless snippet == snippet_blob[:snippet_chunks].last
-                        %a
-                          = "..."
-                    - else
-                      .file-content.code
-                        .nothing-here-block Empty file
+                      = "..."
+                - else
+                  .file-content.code
+                    .nothing-here-block Empty file
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 2bc98983d672d2f7e91cdadfeb7b9325852e5871..ee242c94db82baac988bfaddd04ccda78698882d 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,13 +1,12 @@
-.file-content.code.js-syntax-highlight{ class: user_color_scheme }
+.file-content.code.js-syntax-highlight
   .line-numbers
     - if blob.data.present?
       - blob.data.lines.each_index do |index|
         - offset = defined?(first_line_number) ? first_line_number : 1
         - i = index + offset
         -# We're not using `link_to` because it is too slow once we get to thousands of lines.
-        %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
+        %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
           %i.fa.fa-link
           = i
   .blob-content{data: {blob_id: blob.id}}
-    :preserve
-      #{highlight(blob.name, blob.data)}
+    = highlight(blob.name, blob.data)
diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml
index 3596aabe309d14fc23ea7015fd834436e0d5443c..09edf4000d5f911b94e64323af6bace5ebb25dca 100644
--- a/app/views/shared/_promo.html.haml
+++ b/app/views/shared/_promo.html.haml
@@ -1,5 +1,5 @@
 .gitlab-promo
   = link_to 'Homepage', promo_url
-  = link_to "Blog", promo_url + '/blog/'
-  = link_to "@gitlab", "https://twitter.com/gitlab"
-  = link_to "Requests", "http://feedback.gitlab.com/"
+  = link_to 'Blog', promo_url + '/blog/'
+  = link_to '@gitlab', 'https://twitter.com/gitlab'
+  = link_to 'Requests', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#feature-proposals'
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index af3d35de325d7465360f5764f600d5e2aa7b307c..f09ab25276da1d33263420e4ed8fca6e2c6d9758 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -1,6 +1,6 @@
 .dropdown.inline.prepend-left-10
   %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
-    %span.light sort:
+    %span.light
     - if @sort.present?
       = sort_options_hash[@sort]
     - else
diff --git a/app/views/shared/issuable/_search_form.html.haml b/app/views/shared/issuable/_search_form.html.haml
index 3a5ad00aa91a7c63b045bab87a6b292a5f296ea9..6672ea796294d3e7c7e226800d4edd170706e3c7 100644
--- a/app/views/shared/issuable/_search_form.html.haml
+++ b/app/views/shared/issuable/_search_form.html.haml
@@ -1,6 +1,6 @@
 = form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do
   .append-right-10.hidden-xs.hidden-sm
-    = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input', spellcheck: false }
+    = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input', spellcheck: false }
     = hidden_field_tag :state, params['state']
     = hidden_field_tag :scope, params['scope']
     = hidden_field_tag :assignee_id, params['assignee_id']
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index e5ffe1e29ae688ce52c83a04ce200e8a5a22b367..b3f45373f6bda60f0244da84608fce9f6db8d3bf 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -1,14 +1,18 @@
 - projects_limit = 20 unless local_assigns[:projects_limit]
 - avatar = true unless local_assigns[:avatar] == false
+- use_creator_avatar = false unless local_assigns[:use_creator_avatar] == true
 - stars = true unless local_assigns[:stars] == false
+- forks = false unless local_assigns[:forks] == true
 - ci = false unless local_assigns[:ci] == true
 - skip_namespace = false unless local_assigns[:skip_namespace] == true
+- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
 
 %ul.projects-list
   - projects.each_with_index do |project, i|
     - css_class = (i >= projects_limit) ? 'hide' : nil
     = render "shared/projects/project", project: project, skip_namespace: skip_namespace,
-      avatar: avatar, stars: stars, css_class: css_class, ci: ci
+      avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
+      forks: forks, show_last_commit_as_description: show_last_commit_as_description
 
   - if projects.size > projects_limit
     %li.bottom.center
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 5db8056b77cb51f1de80b0f0d09840029cd7032d..2aeeed63c95f5abe975bf47e3c3f5373345b9985 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -1,9 +1,11 @@
 - avatar = true unless local_assigns[:avatar] == false
 - stars = true unless local_assigns[:stars] == false
+- forks = false unless local_assigns[:forks] == true
 - ci = false unless local_assigns[:ci] == true
 - skip_namespace = false unless local_assigns[:skip_namespace] == true
 - css_class = '' unless local_assigns[:css_class]
-- css_class += " no-description" unless project.description.present?
+- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
+- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
 - ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit
 - cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.2']
 - cache_key.push(ci_commit.status) if ci_commit
@@ -13,7 +15,10 @@
     = link_to project_path(project), class: dom_class(project) do
       - if avatar
         .dash-project-avatar
-          = project_icon(project, alt: '', class: 'avatar project-avatar s46')
+          - if use_creator_avatar
+            = image_tag avatar_icon(project.creator.email, 46), class: "avatar s46", alt:''
+          - else
+            = project_icon(project, alt: '', class: 'avatar project-avatar s46')
       %span.project-full-name
         %span.namespace-name
           - if project.namespace && !skip_namespace
@@ -26,10 +31,18 @@
       - if ci_commit
         = render_ci_status(ci_commit)
         &nbsp;
+      - if forks
+        %span
+          = icon('code-fork')
+          = project.forks_count
       - if stars
         %span
-          %i.fa.fa-star
+          = icon('star')
           = project.star_count
-    - if project.description.present?
+    - if show_last_commit_as_description
+      .project-description
+        = link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit),
+          class: "commit-row-message"
+    - elsif project.description.present?
       .project-description
         = markdown(project.description, pipeline: :description)
diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml
index d26a99bb14c86b13b6db3a4b6d77ac08a1f3c28b..e0e41fc4bea4e6ff2ba24b9cabf3efc750d6be69 100644
--- a/app/views/shared/snippets/_blob.html.haml
+++ b/app/views/shared/snippets/_blob.html.haml
@@ -3,8 +3,7 @@
     .file-content.wiki
       = render_markup(@snippet.file_name, @snippet.data)
   - else
-    .file-content.code
-      = render 'shared/file_highlight', blob: @snippet
+    = render 'shared/file_highlight', blob: @snippet
 - else
   .file-content.code
     .nothing-here-block Empty file
diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder
index 114d1e7a379cb5fc3f0278af6c1b4aeab161aaa6..e9e466c6350361697d0ddc605774108af483803d 100644
--- a/app/views/users/show.atom.builder
+++ b/app/views/users/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
   xml.link    href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
   xml.link    href: user_url(@user), rel: "alternate", type: "text/html"
   xml.id      user_url(@user)
-  xml.updated @events.latest_update_time.xmlschema if @events.any?
+  xml.updated @events[0].updated_at.xmlschema if @events[0]
 
   @events.each do |event|
     event_to_atom(xml, event)
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index b1f8645eea0545291a00421e3c147b39012d3371..91c5b7eac5e0801af09c77287599e9b32441adcf 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -7,7 +7,7 @@
 
   - if current_user
     .awards-controls
-      %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"}
+      %a.add-award{"href" => "#"}
         = icon('smile-o')
       .emoji-menu
         .emoji-menu-content
diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1b3232cd36521d62fbe3d9dcc726b62d23d221a2
--- /dev/null
+++ b/app/workers/new_note_worker.rb
@@ -0,0 +1,12 @@
+class NewNoteWorker
+  include Sidekiq::Worker
+
+  sidekiq_options queue: :default
+
+  def perform(note_id, note_params)
+    note = Note.find(note_id)
+
+    NotificationService.new.new_note(note)
+    Notes::PostProcessService.new(note).execute
+  end
+end
diff --git a/app/workers/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d06e448029288aec11b98943158c5b45be0a0aa8
--- /dev/null
+++ b/app/workers/project_destroy_worker.rb
@@ -0,0 +1,17 @@
+class ProjectDestroyWorker
+  include Sidekiq::Worker
+
+  sidekiq_options queue: :default
+
+  def perform(project_id, user_id, params)
+    begin
+      project = Project.find(project_id)
+    rescue ActiveRecord::RecordNotFound
+      return
+    end
+
+    user = User.find(user_id)
+
+    ::Projects::DestroyService.new(project, user, params).execute
+  end
+end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index d18c0706b303d7722585914633d43ac8d0aaed0f..e295a9ddd1425e8895cb20ad6c7f77b7e837e5fb 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -4,52 +4,20 @@ class RepositoryImportWorker
 
   sidekiq_options queue: :gitlab_shell
 
-  def perform(project_id)
-    project = Project.find(project_id)
+  attr_accessor :project, :current_user
 
-    if project.import_url == Project::UNKNOWN_IMPORT_URL
-      # In this case, we only want to import issues, not a repository.
-      unless project.create_repository
-        project.update(import_error: "The repository could not be created.")
-        project.import_fail
-        return
-      end
-    else
-      begin
-        gitlab_shell.import_repository(project.path_with_namespace, project.import_url)
-      rescue Gitlab::Shell::Error => e
-        project.update(import_error: e.message)
-        project.import_fail
-        return
-      end
-    end
+  def perform(project_id)
+    @project = Project.find(project_id)
+    @current_user = @project.creator
 
-    data_import_result =
-      case project.import_type
-      when 'github'
-        Gitlab::GithubImport::Importer.new(project).execute
-      when 'gitlab'
-        Gitlab::GitlabImport::Importer.new(project).execute
-      when 'bitbucket'
-        Gitlab::BitbucketImport::Importer.new(project).execute
-      when 'google_code'
-        Gitlab::GoogleCodeImport::Importer.new(project).execute
-      when 'fogbugz'
-        Gitlab::FogbugzImport::Importer.new(project).execute
-      else
-        true
-      end
+    result = Projects::ImportService.new(project, current_user).execute
 
-    unless data_import_result
-      project.update(import_error: "The remote issue data could not be imported.")
+    if result[:status] == :error
+      project.update(import_error: result[:message])
       project.import_fail
       return
     end
 
-    if project.import_type == 'bitbucket'
-      Gitlab::BitbucketImport::KeyDeleter.new(project).execute
-    end
-
     project.import_finish
   end
 end
diff --git a/config.ru b/config.ru
index a2525c813618ca8500a12c8009618f6fe1fd7bd3..065ce59932f2ff8c832589bd20e19ae4defc4394 100644
--- a/config.ru
+++ b/config.ru
@@ -7,8 +7,11 @@ if defined?(Unicorn)
     # Unicorn self-process killer
     require 'unicorn/worker_killer'
 
+    min = (ENV['GITLAB_UNICORN_MEMORY_MIN'] || 300 * 1 << 20).to_i
+    max = (ENV['GITLAB_UNICORN_MEMORY_MAX'] || 350 * 1 << 20).to_i
+
     # Max memory size (RSS) per worker
-    use Unicorn::WorkerKiller::Oom, (200 * (1 << 20)), (250 * (1 << 20))
+    use Unicorn::WorkerKiller::Oom, min, max
   end
 end
 
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 04a7c16ebdec1d121bc47cded1b6a0ab63b39057..d8170557f7ea2955e4cce233869bf7eb8fc195de 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -176,7 +176,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? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z]*-\d*))+)' 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][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
 Settings.gitlab['default_projects_features'] ||= {}
 Settings.gitlab['webhook_timeout'] ||= 10
 Settings.gitlab['max_attachment_size'] ||= 10
diff --git a/config/initializers/haml.rb b/config/initializers/haml.rb
index 7e8ddb3716bad55ceaa5fef99c9f2334c8afa07e..1516476815a56e96907396b699a59de3b2f931c4 100644
--- a/config/initializers/haml.rb
+++ b/config/initializers/haml.rb
@@ -1 +1,7 @@
 Haml::Template.options[:ugly] = true
+
+# Remove the `:coffee` and `:coffeescript` filters
+#
+# See https://git.io/vztMu and http://stackoverflow.com/a/17571242/223897
+Haml::Filters.remove_filter('coffee')
+Haml::Filters.remove_filter('coffeescript')
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index b1fe36dc21c47bc04da71b80c6441edbaedf8c89..0945b93ed5d94331e8c3d464d10b0c29e676fee2 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -49,6 +49,7 @@ if Gitlab::Metrics.enabled?
     config.instrument_instance_methods(Gitlab::Shell)
 
     config.instrument_methods(Gitlab::Git)
+    config.instrument_instance_methods(Gitlab::Git::Repository)
 
     Gitlab::Git.constants.each do |name|
       const = Gitlab::Git.const_get(name)
diff --git a/config/initializers/monkey_patch.rb b/config/initializers/monkey_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..62b05a55285486f6fb1a111b1027af260fb08570
--- /dev/null
+++ b/config/initializers/monkey_patch.rb
@@ -0,0 +1,48 @@
+## This patch is from rails 4.2-stable. Remove it when 4.2.6 is released
+## https://github.com/rails/rails/issues/21108
+
+module ActiveRecord
+  module ConnectionAdapters
+    class AbstractMysqlAdapter < AbstractAdapter
+      # SHOW VARIABLES LIKE 'name'
+      def show_variable(name)
+        variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
+        variables.first['Value'] unless variables.empty?
+      rescue ActiveRecord::StatementInvalid
+        nil
+      end
+
+      
+      # MySQL is too stupid to create a temporary table for use subquery, so we have
+      # to give it some prompting in the form of a subsubquery. Ugh!
+      def subquery_for(key, select)
+        subsubselect = select.clone
+        subsubselect.projections = [key]
+
+        subselect = Arel::SelectManager.new(select.engine)
+        subselect.project Arel.sql(key.name)
+        # Materialized subquery by adding distinct
+        # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
+        subselect.from subsubselect.distinct.as('__active_record_temp')
+      end
+    end
+  end
+end
+
+module ActiveRecord
+  module ConnectionAdapters
+    class MysqlAdapter < AbstractMysqlAdapter
+      ADAPTER_NAME = 'MySQL'.freeze
+
+      # Get the client encoding for this database
+      def client_encoding
+        return @client_encoding if @client_encoding
+
+        result = exec_query(
+          "select @@character_set_client",
+          'SCHEMA')
+        @client_encoding = ENCODINGS[result.rows.last.last]
+      end
+    end
+  end
+end
diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d0630b9fa0789ba6daf52511475dd843685cea22
--- /dev/null
+++ b/config/initializers/sentry.rb
@@ -0,0 +1,19 @@
+# Be sure to restart your server when you modify this file.
+
+require 'gitlab/current_settings'
+include Gitlab::CurrentSettings
+
+if Rails.env.production?
+  # allow it to fail: it may do so when create_from_defaults is executed before migrations are actually done
+  begin
+    sentry_enabled = current_application_settings.sentry_enabled
+  rescue
+    sentry_enabled = false
+  end
+
+  if sentry_enabled
+    Raven.configure do |config|
+      config.dsn = current_application_settings.sentry_dsn
+    end
+  end
+end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index f6cfb5efd2ada02443fe7939ad58f9b5922d719e..cedb5e207bd3798f0ff2a8e6651937ba368d2b8a 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -8,3 +8,7 @@ en:
       wrong_size: "is the wrong size (should be %{file_size})"
       size_too_small: "is too small (should be at least %{file_size})"
       size_too_big: "is too big (should be at most %{file_size})"
+  views:
+    pagination:
+      previous: "Prev"
+      next: "Next"
diff --git a/config/routes.rb b/config/routes.rb
index 75418db8d2584fdad5ecee5323ec663d53ccfdfa..54cc338b605f73153bbc0bcf09eded62cf9ed4ad 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -490,7 +490,7 @@ Rails.application.routes.draw do
         end
 
         resource  :avatar, only: [:show, :destroy]
-        resources :commit, only: [:show], constraints: { id: /[[:alnum:]]{6,40}/ } do
+        resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
           member do
             get :branches
             get :builds
@@ -554,7 +554,7 @@ Rails.application.routes.draw do
           end
         end
 
-        resource :fork, only: [:new, :create]
+        resources :forks, only: [:index, :new, :create]
         resource :import, only: [:new, :create, :show]
 
         resources :refs, only: [] do
diff --git a/db/migrate/20160118155830_add_sentry_to_application_settings.rb b/db/migrate/20160118155830_add_sentry_to_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fa7ff9d92289c54297abc4f1424f21ea0d3b5a03
--- /dev/null
+++ b/db/migrate/20160118155830_add_sentry_to_application_settings.rb
@@ -0,0 +1,8 @@
+class AddSentryToApplicationSettings < ActiveRecord::Migration
+  def change
+    change_table :application_settings do |t|
+      t.boolean :sentry_enabled, default: false
+      t.string :sentry_dsn
+    end
+  end
+end
diff --git a/db/migrate/20160118232755_add_ip_blocking_settings_to_application_settings.rb b/db/migrate/20160118232755_add_ip_blocking_settings_to_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..26606b10b54d66a6d02090892c9f34d6afefdeaa
--- /dev/null
+++ b/db/migrate/20160118232755_add_ip_blocking_settings_to_application_settings.rb
@@ -0,0 +1,6 @@
+class AddIpBlockingSettingsToApplicationSettings < ActiveRecord::Migration
+  def change
+    add_column :application_settings, :ip_blocking_enabled, :boolean, default: false
+    add_column :application_settings, :dnsbl_servers_list, :text
+  end
+end
diff --git a/db/migrate/20160120172143_add_base_commit_sha_to_merge_request_diffs.rb b/db/migrate/20160120172143_add_base_commit_sha_to_merge_request_diffs.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d6c6aa4a4e83d112f6ff044f9549139644d4e69e
--- /dev/null
+++ b/db/migrate/20160120172143_add_base_commit_sha_to_merge_request_diffs.rb
@@ -0,0 +1,5 @@
+class AddBaseCommitShaToMergeRequestDiffs < ActiveRecord::Migration
+  def change
+    add_column :merge_request_diffs, :base_commit_sha, :string
+  end
+end
diff --git a/db/migrate/20160122185421_add_pending_delete_to_project.rb b/db/migrate/20160122185421_add_pending_delete_to_project.rb
new file mode 100644
index 0000000000000000000000000000000000000000..046a5d8fc3214ff219c997fed7b34b52be784f6f
--- /dev/null
+++ b/db/migrate/20160122185421_add_pending_delete_to_project.rb
@@ -0,0 +1,5 @@
+class AddPendingDeleteToProject < ActiveRecord::Migration
+  def change
+    add_column :projects, :pending_delete, :boolean, default: false
+  end
+end
diff --git a/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb b/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..41821cdcc42aeef9923d3b623224683f032ac518
--- /dev/null
+++ b/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb
@@ -0,0 +1,6 @@
+class RemoveIpBlockingSettingsFromApplicationSettings < ActiveRecord::Migration
+  def change
+    remove_column :application_settings, :ip_blocking_enabled, :boolean, default: false
+    remove_column :application_settings, :dnsbl_servers_list, :text
+  end
+end
diff --git a/db/migrate/20160128233227_change_lfs_objects_size_column.rb b/db/migrate/20160128233227_change_lfs_objects_size_column.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e7fd1f71777790be3a0faed121fff101207be8d7
--- /dev/null
+++ b/db/migrate/20160128233227_change_lfs_objects_size_column.rb
@@ -0,0 +1,5 @@
+class ChangeLfsObjectsSizeColumn < ActiveRecord::Migration
+  def change
+    change_column :lfs_objects, :size, :integer, limit: 8
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index dc7cb9f667fb271f155c4cbd74337b6bdc93d61d..2ad2c23fba52e8d3eed6842b83830fa5ac49239d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20160119145451) do
+ActiveRecord::Schema.define(version: 20160128233227) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -62,6 +62,8 @@ ActiveRecord::Schema.define(version: 20160119145451) do
     t.string   "recaptcha_private_key"
     t.integer  "metrics_port",                      default: 8089
     t.integer  "metrics_sample_interval",           default: 15
+    t.boolean  "sentry_enabled",                    default: false
+    t.string   "sentry_dsn"
   end
 
   create_table "audit_events", force: :cascade do |t|
@@ -443,8 +445,8 @@ ActiveRecord::Schema.define(version: 20160119145451) do
   add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
 
   create_table "lfs_objects", force: :cascade do |t|
-    t.string   "oid",        null: false
-    t.integer  "size",       null: false
+    t.string   "oid",                  null: false
+    t.integer  "size",       limit: 8, null: false
     t.datetime "created_at"
     t.datetime "updated_at"
     t.string   "file"
@@ -490,6 +492,7 @@ ActiveRecord::Schema.define(version: 20160119145451) do
     t.integer  "merge_request_id", null: false
     t.datetime "created_at"
     t.datetime "updated_at"
+    t.string   "base_commit_sha"
   end
 
   add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree
@@ -674,6 +677,7 @@ ActiveRecord::Schema.define(version: 20160119145451) do
     t.string   "build_coverage_regex"
     t.boolean  "build_allow_git_fetch",  default: true,     null: false
     t.integer  "build_timeout",          default: 3600,     null: false
+    t.boolean  "pending_delete",         default: false
   end
 
   add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
@@ -725,19 +729,19 @@ ActiveRecord::Schema.define(version: 20160119145451) do
     t.string   "type"
     t.string   "title"
     t.integer  "project_id"
-    t.datetime "created_at",                                           null: false
-    t.datetime "updated_at",                                           null: false
-    t.boolean  "active",                                               null: false
+    t.datetime "created_at",                               null: false
+    t.datetime "updated_at",                               null: false
+    t.boolean  "active",                                   null: false
     t.text     "properties"
-    t.boolean  "template",                          default: false
-    t.boolean  "push_events",                       default: true
-    t.boolean  "issues_events",                     default: true
-    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
-    t.string   "category",                          default: "common", null: false
-    t.boolean  "default",                           default: false
+    t.boolean  "template",              default: false
+    t.boolean  "push_events",           default: true
+    t.boolean  "issues_events",         default: true
+    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
+    t.string   "category",              default: "common", null: false
+    t.boolean  "default",               default: false
   end
 
   add_index "services", ["category"], name: "index_services_on_category", using: :btree
@@ -854,7 +858,7 @@ ActiveRecord::Schema.define(version: 20160119145451) do
     t.boolean  "hide_project_limit",          default: false
     t.string   "unlock_token"
     t.datetime "otp_grace_period_started_at"
-    t.boolean  "ldap_email",                              default: false, null: false
+    t.boolean  "ldap_email",                  default: false, null: false
   end
 
   add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/README.md b/doc/README.md
index 7d4f84857e001211ad6cc3ec4b17a5b2af1398bc..5089e1e70f6c2f9028e17eea110383294c44a5f8 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -30,6 +30,7 @@
 - [User permissions](ci/permissions/README.md)
 - [API](ci/api/README.md)
 - [Triggering builds through the API](ci/triggers/README.md)
+- [Build artifacts](ci/build_artifacts/README.md)
 
 ### CI Languages
 
@@ -53,6 +54,7 @@
 
 - [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
 - [Install](install/README.md) Requirements, directory structures and installation from source.
+- [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components
 - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
 - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
 - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
@@ -67,6 +69,8 @@
 - [Reply by email](incoming_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails.
 - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE.
 - [Git LFS configuration](workflow/lfs/lfs_administration.md)
+- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
+- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics
 
 ## Contributor documentation
 
diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md
index 1eb3a74d304a6cbceaa4f207a8cbc2b76314d100..0faef526d43c3c1818c87a5e276bfd08b0fe07e2 100644
--- a/doc/administration/environment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -17,6 +17,8 @@ DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5
 GITLAB_EMAIL_FROM | email | Email address used in the "From" field in mails sent by GitLab
 GITLAB_EMAIL_DISPLAY_NAME | string | Name used in the "From" field in mails sent by GitLab
 GITLAB_EMAIL_REPLY_TO | email | Email address used in the "Reply-To" field in mails sent by GitLab
+GITLAB_UNICORN_MEMORY_MIN | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer
+GITLAB_UNICORN_MEMORY_MAX | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
 
 ## Complete database variables
 
@@ -45,6 +47,7 @@ GITLAB_DATABASE_PORT | 5432
 ## Adding more variables
 
 We welcome merge requests to make more settings configurable via variables.
+Please make changes in the file config/initializers/1_settings.rb
 Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}".
 
 ## Omnibus configuration
diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md
new file mode 100644
index 0000000000000000000000000000000000000000..a5fa7d358a2ab55094461f08b2599e185549c82d
--- /dev/null
+++ b/doc/administration/housekeeping.md
@@ -0,0 +1,22 @@
+# Housekeeping
+
+_**Note:** This feature was [introduced][ce-2371] in GitLab 8.4_
+
+---
+
+The housekeeping function runs `git gc` ([man page][man]) on the current
+project Git repository.
+
+`git gc` runs a number of housekeeping tasks, such as compressing file
+revisions (to reduce disk space and increase performance) and removing
+unreachable objects which may have been created from prior invocations of
+`git add`.
+
+You can find this option under your **[Project] > Settings**.
+
+---
+
+![Housekeeping settings](img/housekeeping_settings.png)
+
+[ce-2371]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2371 "Housekeeping merge request"
+[man]: https://www.kernel.org/pub/software/scm/git/docs/git-gc.html "git gc man page"
diff --git a/doc/administration/img/housekeeping_settings.png b/doc/administration/img/housekeeping_settings.png
new file mode 100644
index 0000000000000000000000000000000000000000..f7c5bc44367e82ed3a62faa3685c50612f8fb315
Binary files /dev/null and b/doc/administration/img/housekeeping_settings.png differ
diff --git a/doc/administration/restart_gitlab.md b/doc/administration/restart_gitlab.md
new file mode 100644
index 0000000000000000000000000000000000000000..483060395dd2fc996f90d54744f137a580a5cf03
--- /dev/null
+++ b/doc/administration/restart_gitlab.md
@@ -0,0 +1,145 @@
+# How to restart GitLab
+
+Depending on how you installed GitLab, there are different methods to restart
+its service(s).
+
+If you want the TL;DR versions, jump to:
+
+- [Omnibus GitLab restart](#omnibus-gitlab-restart)
+- [Omnibus GitLab reconfigure](#omnibus-gitlab-reconfigure)
+- [Source installation restart](#installations-from-source)
+
+## Omnibus installations
+
+If you have used the [Omnibus packages][omnibus-dl] to install GitLab, then
+you should already have `gitlab-ctl` in your `PATH`.
+
+`gitlab-ctl` interacts with the Omnibus packages and can be used to restart the
+GitLab Rails application (Unicorn) as well as the other components, like:
+
+- GitLab Workhorse
+- Sidekiq
+- PostgreSQL (if you are using the bundled one)
+- NGINX (if you are using the bundled one)
+- Redis (if you are using the bundled one)
+- [Mailroom][]
+- Logrotate
+
+### Omnibus GitLab restart
+
+There may be times in the documentation where you will be asked to _restart_
+GitLab. In that case, you need to run the following command:
+
+```bash
+sudo gitlab-ctl restart
+```
+
+The output should be similar to this:
+
+```
+ok: run: gitlab-workhorse: (pid 11291) 1s
+ok: run: logrotate: (pid 11299) 0s
+ok: run: mailroom: (pid 11306) 0s
+ok: run: nginx: (pid 11309) 0s
+ok: run: postgresql: (pid 11316) 1s
+ok: run: redis: (pid 11325) 0s
+ok: run: sidekiq: (pid 11331) 1s
+ok: run: unicorn: (pid 11338) 0s
+```
+
+To restart a component separately, you can append its service name to the
+`restart` command. For example, to restart **only** NGINX you would run:
+
+```bash
+sudo gitlab-ctl restart nginx
+```
+
+To check the status of GitLab services, run:
+
+```bash
+sudo gitlab-ctl status
+```
+
+Notice that all services say `ok: run`.
+
+Sometimes, components time out during the restart and sometimes they get stuck.
+In that case, you can use `gitlab-ctl kill <service>` to send the `SIGKILL`
+signal to the service, for example `sidekiq`. After that, a restart should
+perform fine.
+
+As a last resort, you can try to
+[reconfigure GitLab](#omnibus-gitlab-reconfigure) instead.
+
+### Omnibus GitLab reconfigure
+
+There may be times in the documentation where you will be asked to _reconfigure_
+GitLab. Remember that this method applies only for the Omnibus packages.
+
+Reconfigure Omnibus GitLab with:
+
+```bash
+sudo gitlab-ctl reconfigure
+```
+
+Reconfiguring GitLab should occur in the event that something in its
+configuration (`/etc/gitlab/gitlab.rb`) has changed.
+
+When you run this command, [Chef], the underlying configuration management
+application that powers Omnibus GitLab, will make sure that all directories,
+permissions, services, etc., are in place and in the same shape that they were
+initially shipped.
+
+It will also restart GitLab components where needed, if any of their
+configuration files have changed.
+
+If you manually edit any files in `/var/opt/gitlab` that are managed by Chef,
+running reconfigure will revert the changes AND restart the services that
+depend on those files.
+
+## Installations from source
+
+If you have followed the official installation guide to [install GitLab from
+source][install], run the following command to restart GitLab:
+
+```
+sudo service gitlab restart
+```
+
+The output should be similar to this:
+
+```
+Shutting down GitLab Unicorn
+Shutting down GitLab Sidekiq
+Shutting down GitLab Workhorse
+Shutting down GitLab MailRoom
+...
+GitLab is not running.
+Starting GitLab Unicorn
+Starting GitLab Sidekiq
+Starting GitLab Workhorse
+Starting GitLab MailRoom
+...
+The GitLab Unicorn web server with pid 28059 is running.
+The GitLab Sidekiq job dispatcher with pid 28176 is running.
+The GitLab Workhorse with pid 28122 is running.
+The GitLab MailRoom email processor with pid 28114 is running.
+GitLab and all its components are up and running.
+```
+
+This should restart Unicorn, Sidekiq, GitLab Workhorse and [Mailroom][]
+(if enabled). The init service file that does all the magic can be found on
+your server in `/etc/init.d/gitlab`.
+
+---
+
+If you are using other init systems, like systemd, you can check the
+[GitLab Recipes][gl-recipes] repository for some unofficial services. These are
+**not** officially supported so use them at your own risk.
+
+
+[omnibus-dl]: https://about.gitlab.com/downloads/ "Download the Omnibus packages"
+[install]: ../install/installation.md "Documentation to install GitLab from source"
+[mailroom]: ../incoming_email/README.md "Used for replying by email in GitLab issues and merge requests"
+[chef]: https://www.chef.io/chef/ "Chef official website"
+[src-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab "GitLab init service file"
+[gl-recipes]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/init "GitLab Recipes repository"
diff --git a/doc/api/README.md b/doc/api/README.md
index 4d2fb582833aaa01c1f3fc15105f0767cbe2cfaa..9f3ad12632012e9eb8bb52d3c53d0bf6038116d0 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -1,7 +1,13 @@
 # GitLab API
 
+Automate GitLab via a simple and powerful API. All definitions can be found
+under [`/lib/api`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api).
+
 ## Resources
 
+Documentation for various API resources can be found separately in the
+following locations:
+
 - [Users](users.md)
 - [Session](session.md)
 - [Projects](projects.md) including setting Webhooks
@@ -27,16 +33,15 @@
 - [Build triggers](build_triggers.md)
 - [Build Variables](build_variables.md)
 
-## Clients
-
-Find API Clients for GitLab [on our website](https://about.gitlab.com/applications/#api-clients).
-You can use [GitLab as an OAuth2 client](oauth2.md) to make API calls.
+## Authentication
 
-## Introduction
+All API requests require authentication. You need to pass a `private_token`
+parameter via query string or header. If passed as a header, the header name
+must be `PRIVATE-TOKEN` (uppercase and with a dash instead of an underscore).
+You can find or reset your private token in your account page (`/profile/account`).
 
-All API requests require authentication. You need to pass a `private_token` parameter by URL or header. If passed as header, the header name must be "PRIVATE-TOKEN" (capital and with dash instead of underscore). You can find or reset your private token in your profile.
-
-If no, or an invalid, `private_token` is provided then an error message will be returned with status code 401:
+If `private_token` is invalid or omitted, then an error message will be
+returned with status code `401`:
 
 ```json
 {
@@ -44,71 +49,83 @@ If no, or an invalid, `private_token` is provided then an error message will be
 }
 ```
 
-API requests should be prefixed with `api` and the API version. The API version is defined in `lib/api.rb`.
+API requests should be prefixed with `api` and the API version. The API version
+is defined in [`lib/api.rb`][lib-api-url].
 
 Example of a valid API request:
 
-```
-GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U
+```shell
+GET https://gitlab.example.com/api/v3/projects?private_token=9koXpg98eAheJpvBs5tK
 ```
 
-Example for a valid API request using curl and authentication via header:
+Example of a valid API request using cURL and authentication via header:
 
-```
-curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" "http://example.com/api/v3/projects"
+```shell
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects"
 ```
 
-The API uses JSON to serialize data. You don't need to specify `.json` at the end of API URL.
+The API uses JSON to serialize data. You don't need to specify `.json` at the
+end of an API URL.
 
 ## Authentication with OAuth2 token
 
-Instead of the private_token you can transmit the OAuth2 access token as a header or as a parameter.
+Instead of the `private_token` you can transmit the OAuth2 access token as a
+header or as a parameter.
 
-### OAuth2 token (as a parameter)
+Example of OAuth2 token as a parameter:
 
-```
-curl https://localhost:3000/api/v3/user?access_token=OAUTH-TOKEN
+```shell
+curl https://gitlab.example.com/api/v3/user?access_token=OAUTH-TOKEN
 ```
 
-###  OAuth2 token (as a header)
+Example of OAuth2 token as a header:
 
-```
-curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user
+```shell
+curl -H "Authorization: Bearer OAUTH-TOKEN" https://example.com/api/v3/user
 ```
 
 Read more about [GitLab as an OAuth2 client](oauth2.md).
 
 ## Status codes
 
-The API is designed to return different status codes according to context and action. In this way if a request results in an error the caller is able to get insight into what went wrong, e.g. status code `400 Bad Request` is returned if a required attribute is missing from the request. The following list gives an overview of how the API functions generally behave.
-
-API request types:
-
-- `GET` requests access one or more resources and return the result as JSON
-- `POST` requests return `201 Created` if the resource is successfully created and return the newly created resource as JSON
-- `GET`, `PUT` and `DELETE` return `200 OK` if the resource is accessed, modified or deleted successfully, the (modified) result is returned as JSON
-- `DELETE` requests are designed to be idempotent, meaning a request a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind it is the user is not really interested if the resource existed before or not.
-
-The following list shows the possible return codes for API requests.
-
-Return values:
-
-- `200 OK` - The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON
-- `201 Created` - The `POST` request was successful and the resource is returned as JSON
-- `400 Bad Request` - A required attribute of the API request is missing, e.g. the title of an issue is not given
-- `401 Unauthorized` - The user is not authenticated, a valid user token is necessary, see above
-- `403 Forbidden` - The request is not allowed, e.g. the user is not allowed to delete a project
-- `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found
-- `405 Method Not Allowed` - The request is not supported
-- `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
-- `422 Unprocessable` - The entity could not be processed
-- `500 Server Error` - While handling the request something went wrong on the server side
+The API is designed to return different status codes according to context and
+action. This way, if a request results in an error, the caller is able to get
+insight into what went wrong.
+
+The following table gives an overview of how the API functions generally behave.
+
+| Request type | Description |
+| ------------ | ----------- |
+| `GET`   | Access one or more resources and return the result as JSON. |
+| `POST`  | Return `201 Created` if the resource is successfully created and return the newly created resource as JSON. |
+| `GET` / `PUT` / `DELETE` | Return `200 OK` if the resource is accessed, modified or deleted successfully. The (modified) result is returned as JSON. |
+| `DELETE` | Designed to be idempotent, meaning a request to a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind this, is that the user is not really interested if the resource existed before or not. |
+
+The following table shows the possible return codes for API requests.
+
+| Return values | Description |
+| ------------- | ----------- |
+| `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. |
+| `201 Created` | The `POST` request was successful and the resource is returned as JSON. |
+| `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. |
+| `401 Unauthorized` | The user is not authenticated, a valid [user token](#authentication) is necessary. |
+| `403 Forbidden` | The request is not allowed, e.g., the user is not allowed to delete a project. |
+| `404 Not Found` | A resource could not be accessed, e.g., an ID for a resource could not be found. |
+| `405 Method Not Allowed` | The request is not supported. |
+| `409 Conflict` | A conflicting resource already exists, e.g., creating a project with a name that already exists. |
+| `422 Unprocessable` | The entity could not be processed. |
+| `500 Server Error` | While handling the request something went wrong server-side. |
 
 ## Sudo
 
-All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass  `sudo` parameter by URL or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals).
+All API requests support performing an API call as if you were another user,
+provided your private token is from an administrator account. You need to pass
+the `sudo` parameter either via query string or a header with an ID/username of
+the user you want to perform the operation as. If passed as a header, the
+header name must be `SUDO` (uppercase).
 
-If a non administrative `private_token` is provided then an error message will be returned with status code 403:
+If a non administrative `private_token` is provided, then an error message will
+be returned with status code `403`:
 
 ```json
 {
@@ -116,7 +133,8 @@ If a non administrative `private_token` is provided then an error message will b
 }
 ```
 
-If the sudo user id or username cannot be found then an error message will be returned with status code 404:
+If the sudo user ID or username cannot be found, an error message will be
+returned with status code `404`:
 
 ```json
 {
@@ -124,32 +142,45 @@ If the sudo user id or username cannot be found then an error message will be re
 }
 ```
 
-Example of a valid API with sudo request:
+---
 
-```
-GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U&sudo=username
-```
+Example of a valid API call and a request using cURL with sudo request,
+providing a username:
 
+```shell
+GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=username
 ```
-GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U&sudo=23
+
+```shell
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: username" "https://gitlab.example.com/api/v3/projects"
 ```
 
-Example for a valid API request with sudo using curl and authentication via header:
+Example of a valid API call and a request using cURL with sudo request,
+providing an ID:
 
-```
-curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: username" "http://example.com/api/v3/projects"
+```shell
+GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23
 ```
 
-```
-curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: 23" "http://example.com/api/v3/projects"
+```shell
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v3/projects"
 ```
 
 ## Pagination
 
-When listing resources you can pass the following parameters:
+Sometimes the returned result will span across many pages. When listing
+resources you can pass the following parameters:
+
+| Parameter | Description |
+| --------- | ----------- |
+| `page`    | Page number (default: `1`) |
+| `per_page`| Number of items to list per page (default: `20`, max: `100`) |
 
-- `page` (default: `1`) - page number
-- `per_page` (default: `20`, max: `100`) - number of items to list per page
+In the example below, we list 50 [namespaces](namespaces.md) per page.
+
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/namespaces?per_page=50
+```
 
 ### Pagination Link header
 
@@ -199,65 +230,93 @@ Additional pagination headers are also sent back.
 | `X-Next-Page`   | The index of the next page |
 | `X-Prev-Page`   | The index of the previous page |
 
-## id vs iid
+## `id` vs `iid`
 
-When you work with API you may notice two similar fields in api entities: id and iid. The main difference between them is scope. Example:
+When you work with the API, you may notice two similar fields in API entities:
+`id` and `iid`. The main difference between them is scope.
 
-Issue:
+For example, an issue might have `id: 46` and `iid: 5`.
 
-    id: 46
-    iid: 5
+| Parameter | Description |
+| --------- | ----------- |
+| `id`  | Is unique across all issues and is used for any API call |
+| `iid` | Is unique only in scope of a single project. When you browse issues or merge requests with the Web UI, you see the `iid` |
+
+That means that if you want to get an issue via the API you should use the `id`:
+
+```bash
+GET /projects/42/issues/:id
+```
 
-- id - is unique across all issues. It's used for any api call.
-- iid - is unique only in scope of a single project. When you browse issues or merge requests with Web UI, you see iid.
+On the other hand, if you want to create a link to a web page you should use
+the `iid`:
 
-So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json`. But when you want to create a link to web page - use  `http:://host/project/issues/:iid.json`
+```bash
+GET /projects/42/issues/:iid
+```
 
 ## Data validation and error reporting
 
-When working with the API you may encounter validation errors. In such case, the API will answer with an HTTP `400` status.
+When working with the API you may encounter validation errors, in which case
+the API will answer with an HTTP `400` status.
+
 Such errors appear in two cases:
 
-* A required attribute of the API request is missing, e.g. the title of an issue is not given
-* An attribute did not pass the validation, e.g. user bio is too long
+- A required attribute of the API request is missing, e.g., the title of an
+  issue is not given
+- An attribute did not pass the validation, e.g., user bio is too long
 
 When an attribute is missing, you will get something like:
 
-    HTTP/1.1 400 Bad Request
-    Content-Type: application/json
-
-    {
-        "message":"400 (Bad request) \"title\" not given"
-    }
-
-When a validation error occurs, error messages will be different. They will hold all details of validation errors:
+```
+HTTP/1.1 400 Bad Request
+Content-Type: application/json
+{
+    "message":"400 (Bad request) \"title\" not given"
+}
+```
 
-    HTTP/1.1 400 Bad Request
-    Content-Type: application/json
+When a validation error occurs, error messages will be different. They will
+hold all details of validation errors:
 
-    {
-        "message": {
-            "bio": [
-                "is too long (maximum is 255 characters)"
-            ]
-        }
+```
+HTTP/1.1 400 Bad Request
+Content-Type: application/json
+{
+    "message": {
+        "bio": [
+            "is too long (maximum is 255 characters)"
+        ]
     }
+}
+```
 
-This makes error messages more machine-readable. The format can be described as follow:
+This makes error messages more machine-readable. The format can be described as
+follows:
 
-    {
-        "message": {
+```json
+{
+    "message": {
+        "<property-name>": [
+            "<error-message>",
+            "<error-message>",
+            ...
+        ],
+        "<embed-entity>": {
             "<property-name>": [
                 "<error-message>",
                 "<error-message>",
                 ...
             ],
-            "<embed-entity>": {
-                "<property-name>": [
-                    "<error-message>",
-                    "<error-message>",
-                    ...
-                ],
-            }
         }
     }
+}
+```
+
+## Clients
+
+There are many unofficial GitLab API Clients for most of the popular
+programming languages. Visit the [GitLab website] for a complete list.
+
+[GitLab website]: https://about.gitlab.com/applications/#api-clients "Clients using the GitLab API"
+[lib-api-url]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api/api.rb
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 6a9c10c85206d9ee3f0820332abcbd45782c5a60..abc4732c395584d2ea88ecf3a4a9ef56238bd64c 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -8,13 +8,21 @@ Get a list of repository branches from a project, sorted by name alphabetically.
 GET /projects/:id/repository/branches
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
 
-- `id` (required) - The ID of a project
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches
+```
+
+Example response:
 
 ```json
 [
   {
+    "name": "master",
+    "protected": true,
     "commit": {
       "author_email": "john@example.com",
       "author_name": "John Smith",
@@ -27,10 +35,9 @@ Parameters:
       "parent_ids": [
         "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
       ]
-    },
-    "name": "master",
-    "protected": true
-  }
+    }
+  },
+  ...
 ]
 ```
 
@@ -42,13 +49,21 @@ Get a single project repository branch.
 GET /projects/:id/repository/branches/:branch
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch` | string | yes | The name of the branch |
 
-- `id` (required) - The ID of a project
-- `branch` (required) - The name of the branch
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master
+```
+
+Example response:
 
 ```json
 {
+  "name": "master",
+  "protected": true,
   "commit": {
     "author_email": "john@example.com",
     "author_name": "John Smith",
@@ -61,25 +76,30 @@ Parameters:
     "parent_ids": [
       "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
     ]
-  },
-  "name": "master",
-  "protected": true
+  }
 }
 ```
 
 ## Protect repository branch
 
-Protects a single project repository branch. This is an idempotent function, protecting an already
-protected repository branch still returns a `200 OK` status code.
+Protects a single project repository branch. This is an idempotent function,
+protecting an already protected repository branch still returns a `200 OK`
+status code.
 
 ```
 PUT /projects/:id/repository/branches/:branch/protect
 ```
 
-Parameters:
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect
+```
 
-- `id` (required) - The ID of a project
-- `branch` (required) - The name of the branch
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch` | string | yes | The name of the branch |
+
+Example response:
 
 ```json
 {
@@ -103,17 +123,24 @@ Parameters:
 
 ## Unprotect repository branch
 
-Unprotects a single project repository branch. This is an idempotent function, unprotecting an already
-unprotected repository branch still returns a `200 OK` status code.
+Unprotects a single project repository branch. This is an idempotent function,
+unprotecting an already unprotected repository branch still returns a `200 OK`
+status code.
 
 ```
 PUT /projects/:id/repository/branches/:branch/unprotect
 ```
 
-Parameters:
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/unprotect
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch` | string | yes | The name of the branch |
 
-- `id` (required) - The ID of a project
-- `branch` (required) - The name of the branch
+Example response:
 
 ```json
 {
@@ -141,11 +168,17 @@ Parameters:
 POST /projects/:id/repository/branches
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`          | integer | yes | The ID of a project |
+| `branch_name` | string  | yes | The name of the branch |
+| `ref`         | string  | yes | The branch name or commit SHA to create branch from |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches?branch_name=newbranch&ref=master"
+```
 
-- `id` (required) - The ID of a project
-- `branch_name` (required) - The name of the branch
-- `ref` (required) - Create branch from commit SHA or existing branch
+Example response:
 
 ```json
 {
@@ -162,12 +195,13 @@ Parameters:
       "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
     ]
   },
-  "name": "master",
+  "name": "newbranch",
   "protected": false
 }
 ```
 
-It return 200 if succeed or 400 if failed with error message explaining reason.
+It returns `200` if it succeeds or `400` if failed with an error message
+explaining the reason.
 
 ## Delete repository branch
 
@@ -175,18 +209,22 @@ It return 200 if succeed or 400 if failed with error message explaining reason.
 DELETE /projects/:id/repository/branches/:branch
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer | yes | The ID of a project |
+| `branch`  | string  | yes | The name of the branch |
 
-- `id` (required) - The ID of a project
-- `branch` (required) - The name of the branch
+It returns `200` if it succeeds, `404` if the branch to be deleted does not exist
+or `400` for other reasons. In case of an error, an explaining message is provided.
 
-It return 200 if succeed, 404 if the branch to be deleted does not exist
-or 400 for other reasons. In case of an error, an explaining message is provided.
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches/newbranch"
+```
 
-Success response: 
+Example response:
 
 ```json
 {
-  "branch_name": "my-removed-branch"
+  "branch_name": "newbranch"
 }
 ```
diff --git a/doc/api/builds.md b/doc/api/builds.md
index ecb50754c88c25f84af919e7a9f62c179cd18780..6e64d0966444dbbba07755b29825acc1706c99c9 100644
--- a/doc/api/builds.md
+++ b/doc/api/builds.md
@@ -18,7 +18,7 @@ GET /projects/:id/builds
 ### Example of request
 
 ```
-curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds"
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds"
 ```
 
 ### Example of response
@@ -123,7 +123,7 @@ GET /projects/:id/repository/commits/:sha/builds
 ### Example of request
 
 ```
-curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds"
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds"
 ```
 
 ### Example of response
@@ -213,7 +213,7 @@ GET /projects/:id/builds/:build_id
 ### Example of request
 
 ```
-curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8"
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8"
 ```
 
 ### Example of response
@@ -277,7 +277,7 @@ POST /projects/:id/builds/:build_id/cancel
 ### Example of request
 
 ```
-curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel"
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel"
 ```
 
 ### Example of response
@@ -327,7 +327,7 @@ POST /projects/:id/builds/:build_id/retry
 ### Example of request
 
 ```
-curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry"
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry"
 ```
 
 ### Example of response
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 93d62b751e66ab0743e7ab5a0fa3b324952f371d..e4d436b8e52179882656f2f5a9fbc8642be5858f 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -8,10 +8,16 @@ Get a list of repository commits in a project.
 GET /projects/:id/repository/commits
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits"
+```
 
-- `id` (required) - The ID of a project
-- `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
+Example response:
 
 ```json
 [
@@ -48,8 +54,16 @@ GET /projects/:id/repository/commits/:sha
 
 Parameters:
 
-- `id` (required) - The ID of a project
-- `sha` (required) - The commit hash or name of a repository branch or tag
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `sha` | string | yes | The commit hash or name of a repository branch or tag |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master
+```
+
+Example response:
 
 ```json
 {
@@ -79,8 +93,16 @@ GET /projects/:id/repository/commits/:sha/diff
 
 Parameters:
 
-- `id` (required) - The ID of a project
-- `sha` (required) - The name of a repository branch or tag or if not given the default branch
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `sha` | string | yes | The commit hash or name of a repository branch or tag |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/diff"
+```
+
+Example response:
 
 ```json
 [
@@ -107,8 +129,16 @@ GET /projects/:id/repository/commits/:sha/comments
 
 Parameters:
 
-- `id` (required) - The ID of a project
-- `sha` (required) - The name of a repository branch or tag or if not given the default branch
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `sha` | string | yes | The commit hash or name of a repository branch or tag |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/comments"
+```
+
+Example response:
 
 ```json
 [
@@ -128,39 +158,65 @@ Parameters:
 
 ## Post comment to commit
 
-Adds a comment to a commit. Optionally you can post comments on a specific line of a commit. Therefor both `path`, `line_new` and `line_old` are required.
+Adds a comment to a commit.
+
+In order to post a comment in a particular line of a particular file, you must
+specify the full commit SHA, the `path`, the `line` and `line_type` should be
+`new`.
+
+The comment will be added at the end of the last commit if at least one of the
+cases below is valid:
+
+- the `sha` is instead a branch or a tag and the `line` or `path` are invalid
+- the `line` number is invalid (does not exist)
+- the `path` is invalid (does not exist)
+
+In any of the above cases, the response of `line`, `line_type` and `path` is
+set to `null`.
 
 ```
 POST /projects/:id/repository/commits/:sha/comments
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`        | integer | yes | The ID of a project |
+| `sha`       | string  | yes | The commit SHA or name of a repository branch or tag |
+| `note`      | string  | yes | The text of the comment |
+| `path`      | string  | no  | The file path relative to the repository |
+| `line`      | integer | no  | The line number where the comment should be placed |
+| `line_type` | string  | no  | The line type. Takes `new` or `old` as arguments |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -F "note=Nice picture man\!" -F "path=dudeism.md" -F "line=11" -F "line_type=new" https://gitlab.example.com/api/v3/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/comments
+```
 
-- `id` (required)               - The ID of a project
-- `sha` (required)              - The name of a repository branch or tag or if not given the default branch
-- `note` (required)             - Text of comment
-- `path` (optional)             - The file path
-- `line` (optional)             - The line number
-- `line_type` (optional)        - The line type (new or old)
+Example response:
 
 ```json
 {
-  "author": {
-    "id": 1,
-    "username": "admin",
-    "email": "admin@local.host",
-    "name": "Administrator",
-    "blocked": false,
-    "created_at": "2012-04-29T08:46:00Z"
-  },
-  "note": "text1",
-  "path": "example.rb",
-  "line": 5,
-  "line_type": "new"
+   "author" : {
+      "web_url" : "https://gitlab.example.com/u/thedude",
+      "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png",
+      "username" : "thedude",
+      "state" : "active",
+      "name" : "Jeff Lebowski",
+      "id" : 28
+   },
+   "created_at" : "2016-01-19T09:44:55.600Z",
+   "line_type" : "new",
+   "path" : "dudeism.md",
+   "line" : 11,
+   "note" : "Nice picture man!"
 }
 ```
 
-## Get the status of a commit
+## Commit status
+
+Since GitLab 8.1, this is the new commit status API. The documentation in
+[ci/api/commits](../ci/api/commits.md) is deprecated.
+
+### Get the status of a commit
 
 Get the statuses of a commit in a project.
 
@@ -168,75 +224,116 @@ Get the statuses of a commit in a project.
 GET /projects/:id/repository/commits/:sha/statuses
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer | yes | The ID of a project
+| `sha`     | string  | yes | The commit SHA
+| `ref_name`| string  | no  | The name of a repository branch or tag or, if not given, the default branch
+| `stage`   | string  | no  | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test`
+| `name`    | string  | no  | Filter by [job name](../ci/yaml/README.md#jobs), e.g., `bundler:audit`
+| `all`     | boolean | no  | Return all statuses, not only the latest ones
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/statuses
+```
 
-- `id` (required) - The ID of a project
-- `sha` (required) - The commit SHA
-- `ref` (optional) - Filter by ref name, it can be branch or tag
-- `stage` (optional) - Filter by stage
-- `name` (optional) - Filer by status name, eg. jenkins
-- `all` (optional) - The flag to return all statuses, not only latest ones
+Example response:
 
 ```json
 [
-  {
-    "id": 13,
-    "sha": "b0b3a907f41409829b307a28b82fdbd552ee5a27",
-    "ref": "test",
-    "status": "success",
-    "name": "ci/jenkins",
-    "target_url": "http://jenkins/project/url",
-    "description": "Jenkins success",
-    "created_at": "2015-10-12T09:47:16.250Z",
-    "started_at": "2015-10-12T09:47:16.250Z",
-    "finished_at": "2015-10-12T09:47:16.262Z",
-    "author": {
-      "id": 1,
-      "username": "admin",
-      "email": "admin@local.host",
-      "name": "Administrator",
-      "blocked": false,
-      "created_at": "2012-04-29T08:46:00Z"
-    }
-  }
+   ...
+
+   {
+      "status" : "pending",
+      "created_at" : "2016-01-19T08:40:25.934Z",
+      "started_at" : null,
+      "name" : "bundler:audit",
+      "allow_failure" : true,
+      "author" : {
+         "username" : "thedude",
+         "state" : "active",
+         "web_url" : "https://gitlab.example.com/u/thedude",
+         "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png",
+         "id" : 28,
+         "name" : "Jeff Lebowski"
+      },
+      "description" : null,
+      "sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8",
+      "target_url" : "https://gitlab.example.com/thedude/gitlab-ce/builds/91",
+      "finished_at" : null,
+      "id" : 91,
+      "ref" : "master"
+   },
+   {
+      "started_at" : null,
+      "name" : "flay",
+      "allow_failure" : false,
+      "status" : "pending",
+      "created_at" : "2016-01-19T08:40:25.832Z",
+      "target_url" : "https://gitlab.example.com/thedude/gitlab-ce/builds/90",
+      "id" : 90,
+      "finished_at" : null,
+      "ref" : "master",
+      "sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8",
+      "author" : {
+         "id" : 28,
+         "name" : "Jeff Lebowski",
+         "username" : "thedude",
+         "web_url" : "https://gitlab.example.com/u/thedude",
+         "state" : "active",
+         "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png"
+      },
+      "description" : null
+   },
+
+   ...
 ]
 ```
 
-## Post the status to commit
+### Post the build status to a commit
 
-Adds or updates a status of a commit.
+Adds or updates a build status of a commit.
 
 ```
 POST /projects/:id/statuses/:sha
 ```
 
-- `id` (required) - The ID of a project
-- `sha` (required) - The commit SHA
-- `state` (required) - The state of the status. Can be: pending, running, success, failed, canceled
-- `ref` (optional) - The ref (branch or tag) to which the status refers
-- `name` or `context` (optional) - The label to differentiate this status from the status of other systems. Default: "default"
-- `target_url` (optional) - The target URL to associate with this status
-- `description` (optional) - The short description of the status
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer | yes   | The ID of a project
+| `sha`     | string  | yes   | The commit SHA
+| `state`   | string  | yes   | The state of the status. Can be one of the following: `pending`, `running`, `success`, `failed`, `canceled`
+| `ref`     | string  | no    | The `ref` (branch or tag) to which the status refers
+| `name` or `context` | string  | no | The label to differentiate this status from the status of other systems. Default value is `default`
+| `target_url` |  string  | no  | The target URL to associate with this status
+| `description` | string  | no  | The short description of the status
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/statuses/18f3e63d05582537db6d183d9d557be09e1f90c8?state=success"
+```
+
+Example response:
 
 ```json
 {
-  "id": 13,
-  "sha": "b0b3a907f41409829b307a28b82fdbd552ee5a27",
-  "ref": "test",
-  "status": "success",
-  "name": "ci/jenkins",
-  "target_url": "http://jenkins/project/url",
-  "description": "Jenkins success",
-  "created_at": "2015-10-12T09:47:16.250Z",
-  "started_at": "2015-10-12T09:47:16.250Z",
-  "finished_at": "2015-10-12T09:47:16.262Z",
-  "author": {
-    "id": 1,
-    "username": "admin",
-    "email": "admin@local.host",
-    "name": "Administrator",
-    "blocked": false,
-    "created_at": "2012-04-29T08:46:00Z"
-  }
+   "author" : {
+      "web_url" : "https://gitlab.example.com/u/thedude",
+      "name" : "Jeff Lebowski",
+      "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png",
+      "username" : "thedude",
+      "state" : "active",
+      "id" : 28
+   },
+   "name" : "default",
+   "sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8",
+   "status" : "success",
+   "description" : null,
+   "id" : 93,
+   "target_url" : null,
+   "ref" : null,
+   "started_at" : null,
+   "created_at" : "2016-01-19T09:05:50.355Z",
+   "allow_failure" : false,
+   "finished_at" : "2016-01-19T09:05:50.365Z"
 }
 ```
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index e4492fc609c11faf8d77104f54750ee605f68b67..9da1fe22e615946b20446d2cff64d1ebeca4f4ed 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -8,9 +8,15 @@ Get a list of a project's deploy keys.
 GET /projects/:id/keys
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys"
+```
 
-- `id` (required) - The ID of the project
+Example response:
 
 ```json
 [
@@ -39,8 +45,16 @@ GET /projects/:id/keys/:key_id
 
 Parameters:
 
-- `id` (required) - The ID of the project
-- `key_id` (required) - The ID of the deploy key
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer | yes | The ID of the project |
+| `key_id`  | integer | yes | The ID of the deploy key |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys/11"
+```
+
+Example response:
 
 ```json
 {
@@ -54,17 +68,34 @@ Parameters:
 ## Add deploy key
 
 Creates a new deploy key for a project.
-If deploy key already exists in another project - it will be joined to project but only if original one was is accessible by same user
+
+If the deploy key already exists in another project, it will be joined to current
+project only if original one was is accessible by the same user.
 
 ```
 POST /projects/:id/keys
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`    | integer | yes | The ID of the project |
+| `title` | string  | yes | New deploy key's title |
+| `key`   | string  | yes | New deploy key |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/keys/"
+```
 
-- `id` (required) - The ID of the project
-- `title` (required) - New deploy key's title
-- `key` (required) - New deploy key
+Example response:
+
+```json
+{
+   "key" : "ssh-rsa AAAA...",
+   "id" : 12,
+   "title" : "My deploy key",
+   "created_at" : "2015-08-29T12:44:31.550Z"
+}
+```
 
 ## Delete deploy key
 
@@ -74,7 +105,26 @@ Delete a deploy key from a project
 DELETE /projects/:id/keys/:key_id
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer | yes | The ID of the project |
+| `key_id`  | integer | yes | The ID of the deploy key |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys/13"
+```
 
-- `id` (required) - The ID of the project
-- `key_id` (required) - The ID of the deploy key
+Example response:
+
+```json
+{
+   "updated_at" : "2015-08-29T12:50:57.259Z",
+   "key" : "ssh-rsa AAAA...",
+   "public" : false,
+   "title" : "My deploy key",
+   "user_id" : null,
+   "created_at" : "2015-08-29T12:50:57.259Z",
+   "fingerprint" : "6a:33:1f:74:51:c0:39:81:79:ec:7a:31:f8:40:20:43",
+   "id" : 13
+}
+```
diff --git a/doc/api/issues.md b/doc/api/issues.md
index d407bc35d79dd5009bdd2aa571443fc128a100f8..9e704648b25e989f1b1708d3d45e2cad68fa4889 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -1,9 +1,20 @@
 # Issues
 
+Every API call to issues must be authenticated.
+
+If a user is not a member of a project and the project is private, a `GET`
+request on that project will result to a `404` status code.
+
+## Issues pagination
+
+By default, `GET` requests return 20 results at a time because the API results
+are paginated.
+
+Read more on [pagination](README.md#pagination).
+
 ## List issues
 
-Get all issues created by authenticated user. This function takes pagination parameters
-`page` and `per_page` to restrict the list of issues.
+Get all issues created by the authenticated user.
 
 ```
 GET /issues
@@ -14,81 +25,65 @@ GET /issues?labels=foo,bar
 GET /issues?labels=foo,bar&state=opened
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `state`   | string  | no    | Return all issues or just those that are `opened` or `closed`|
+| `labels`  | string  | no    | Comma-separated list of label names |
+| `order_by`| string  | no    | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort`    | string  | no    | Return requests sorted in `asc` or `desc` order. Default is `desc`  |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/issues
+```
 
-- `state` (optional) - Return `all` issues or just those that are `opened` or `closed`
-- `labels` (optional) - Comma-separated list of label names
-- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
-- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+Example response:
 
 ```json
 [
-  {
-    "id": 43,
-    "iid": 3,
-    "project_id": 8,
-    "title": "4xx/5xx pages",
-    "description": "",
-    "labels": [],
-    "milestone": null,
-    "assignee": null,
-    "author": {
-      "id": 1,
-      "username": "john_smith",
-      "email": "john@example.com",
-      "name": "John Smith",
-      "state": "active",
-      "created_at": "2012-05-23T08:00:58Z"
-    },
-    "state": "closed",
-    "updated_at": "2012-07-02T17:53:12Z",
-    "created_at": "2012-07-02T17:53:12Z"
-  },
-  {
-    "id": 42,
-    "iid": 4,
-    "project_id": 8,
-    "title": "Add user settings",
-    "description": "",
-    "labels": [
-      "feature"
-    ],
-    "milestone": {
-      "id": 1,
-      "title": "v1.0",
-      "description": "",
-      "due_date": "2012-07-20",
-      "state": "reopened",
-      "updated_at": "2012-07-04T13:42:48Z",
-      "created_at": "2012-07-04T13:42:48Z"
-    },
-    "assignee": {
-      "id": 2,
-      "username": "jack_smith",
-      "email": "jack@example.com",
-      "name": "Jack Smith",
-      "state": "active",
-      "created_at": "2012-05-23T08:01:01Z"
-    },
-    "author": {
-      "id": 1,
-      "username": "john_smith",
-      "email": "john@example.com",
-      "name": "John Smith",
-      "state": "active",
-      "created_at": "2012-05-23T08:00:58Z"
-    },
-    "state": "opened",
-    "updated_at": "2012-07-12T13:43:19Z",
-    "created_at": "2012-06-28T12:58:06Z"
-  }
+   {
+      "state" : "opened",
+      "description" : "Ratione dolores corrupti mollitia soluta quia.",
+      "author" : {
+         "state" : "active",
+         "id" : 18,
+         "web_url" : "https://gitlab.example.com/u/eileen.lowe",
+         "name" : "Alexandra Bashirian",
+         "avatar_url" : null,
+         "username" : "eileen.lowe"
+      },
+      "milestone" : {
+         "project_id" : 1,
+         "description" : "Ducimus nam enim ex consequatur cumque ratione.",
+         "state" : "closed",
+         "due_date" : null,
+         "iid" : 2,
+         "created_at" : "2016-01-04T15:31:39.996Z",
+         "title" : "v4.0",
+         "id" : 17,
+         "updated_at" : "2016-01-04T15:31:39.996Z"
+      },
+      "project_id" : 1,
+      "assignee" : {
+         "state" : "active",
+         "id" : 1,
+         "name" : "Administrator",
+         "web_url" : "https://gitlab.example.com/u/root",
+         "avatar_url" : null,
+         "username" : "root"
+      },
+      "updated_at" : "2016-01-04T15:31:51.081Z",
+      "id" : 76,
+      "title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
+      "created_at" : "2016-01-04T15:31:51.081Z",
+      "iid" : 6,
+      "labels" : []
+   },
 ]
 ```
 
 ## List project issues
 
-Get a list of project issues. This function accepts pagination parameters `page` and `per_page`
-to return the list of project issues.
+Get a list of a project's issues.
 
 ```
 GET /projects/:id/issues
@@ -102,67 +97,123 @@ GET /projects/:id/issues?milestone=1.0.0&state=opened
 GET /projects/:id/issues?iid=42
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer | yes   | The ID of a project |
+| `iid`     | integer | no    | Return the issue having the given `iid` |
+| `state`   | string  | no    | Return all issues or just those that are `opened` or `closed`|
+| `labels`  | string  | no    | Comma-separated list of label names |
+| `milestone` | string| no    | The milestone title |
+| `order_by`| string  | no    | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort`    | string  | no    | Return requests sorted in `asc` or `desc` order. Default is `desc`  |
+
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues
+```
+
+Example response:
 
-- `id` (required) - The ID of a project
-- `iid` (optional) - Return the issue having the given `iid`
-- `state` (optional) - Return `all` issues or just those that are `opened` or `closed`
-- `labels` (optional) - Comma-separated list of label names
-- `milestone` (optional) - Milestone title
-- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
-- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+```json
+[
+   {
+      "project_id" : 4,
+      "milestone" : {
+         "due_date" : null,
+         "project_id" : 4,
+         "state" : "closed",
+         "description" : "Rerum est voluptatem provident consequuntur molestias similique ipsum dolor.",
+         "iid" : 3,
+         "id" : 11,
+         "title" : "v3.0",
+         "created_at" : "2016-01-04T15:31:39.788Z",
+         "updated_at" : "2016-01-04T15:31:39.788Z"
+      },
+      "author" : {
+         "state" : "active",
+         "web_url" : "https://gitlab.example.com/u/root",
+         "avatar_url" : null,
+         "username" : "root",
+         "id" : 1,
+         "name" : "Administrator"
+      },
+      "description" : "Omnis vero earum sunt corporis dolor et placeat.",
+      "state" : "closed",
+      "iid" : 1,
+      "assignee" : {
+         "avatar_url" : null,
+         "web_url" : "https://gitlab.example.com/u/lennie",
+         "state" : "active",
+         "username" : "lennie",
+         "id" : 9,
+         "name" : "Dr. Luella Kovacek"
+      },
+      "labels" : [],
+      "id" : 41,
+      "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
+      "updated_at" : "2016-01-04T15:31:46.176Z",
+      "created_at" : "2016-01-04T15:31:46.176Z"
+   }
+]
+```
 
 ## Single issue
 
-Gets a single project issue.
+Get a single project issue.
 
 ```
 GET /projects/:id/issues/:issue_id
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer | yes   | The ID of a project |
+| `issue_id`| integer | yes   | The ID of a project's issue |
 
-- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of a project issue
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/41
+```
+
+Example response:
 
 ```json
 {
-  "id": 42,
-  "iid": 3,
-  "project_id": 8,
-  "title": "Add user settings",
-  "description": "",
-  "labels": [
-    "feature"
-  ],
-  "milestone": {
-    "id": 1,
-    "title": "v1.0",
-    "description": "",
-    "due_date": "2012-07-20",
-    "state": "closed",
-    "updated_at": "2012-07-04T13:42:48Z",
-    "created_at": "2012-07-04T13:42:48Z"
-  },
-  "assignee": {
-    "id": 2,
-    "username": "jack_smith",
-    "email": "jack@example.com",
-    "name": "Jack Smith",
-    "state": "active",
-    "created_at": "2012-05-23T08:01:01Z"
-  },
-  "author": {
-    "id": 1,
-    "username": "john_smith",
-    "email": "john@example.com",
-    "name": "John Smith",
-    "state": "active",
-    "created_at": "2012-05-23T08:00:58Z"
-  },
-  "state": "opened",
-  "updated_at": "2012-07-12T13:43:19Z",
-  "created_at": "2012-06-28T12:58:06Z"
+   "project_id" : 4,
+   "milestone" : {
+      "due_date" : null,
+      "project_id" : 4,
+      "state" : "closed",
+      "description" : "Rerum est voluptatem provident consequuntur molestias similique ipsum dolor.",
+      "iid" : 3,
+      "id" : 11,
+      "title" : "v3.0",
+      "created_at" : "2016-01-04T15:31:39.788Z",
+      "updated_at" : "2016-01-04T15:31:39.788Z"
+   },
+   "author" : {
+      "state" : "active",
+      "web_url" : "https://gitlab.example.com/u/root",
+      "avatar_url" : null,
+      "username" : "root",
+      "id" : 1,
+      "name" : "Administrator"
+   },
+   "description" : "Omnis vero earum sunt corporis dolor et placeat.",
+   "state" : "closed",
+   "iid" : 1,
+   "assignee" : {
+      "avatar_url" : null,
+      "web_url" : "https://gitlab.example.com/u/lennie",
+      "state" : "active",
+      "username" : "lennie",
+      "id" : 9,
+      "name" : "Dr. Luella Kovacek"
+   },
+   "labels" : [],
+   "id" : 41,
+   "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
+   "updated_at" : "2016-01-04T15:31:46.176Z",
+   "created_at" : "2016-01-04T15:31:46.176Z"
 }
 ```
 
@@ -170,57 +221,122 @@ Parameters:
 
 Creates a new project issue.
 
+If the operation is successful, a status code of `200` and the newly-created
+issue is returned. If an error occurs, an error number and a message explaining
+the reason is returned.
+
 ```
 POST /projects/:id/issues
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`            | integer | yes | The ID of a project |
+| `title`         | string  | yes | The title of an issue |
+| `description`   | string  | no  | The description of an issue  |
+| `assignee_id`   | integer | no  | The ID of a user to assign issue |
+| `milestone_id`  | integer | no  | The ID of a milestone to assign issue |
+| `labels`        | string  | no  | Comma-separated label names for an issue  |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug
+```
 
-- `id` (required) - The ID of a project
-- `title` (required) - The title of an issue
-- `description` (optional) - The description of an issue
-- `assignee_id` (optional) - The ID of a user to assign issue
-- `milestone_id` (optional) - The ID of a milestone to assign issue
-- `labels` (optional) - Comma-separated label names for an issue
+Example response:
 
-If the operation is successful, 200 and the newly created issue is returned.
-If an error occurs, an error number and a message explaining the reason is returned.
+```json
+{
+   "project_id" : 4,
+   "id" : 84,
+   "created_at" : "2016-01-07T12:44:33.959Z",
+   "iid" : 14,
+   "title" : "Issues with auth",
+   "state" : "opened",
+   "assignee" : null,
+   "labels" : [
+      "bug"
+   ],
+   "author" : {
+      "name" : "Alexandra Bashirian",
+      "avatar_url" : null,
+      "state" : "active",
+      "web_url" : "https://gitlab.example.com/u/eileen.lowe",
+      "id" : 18,
+      "username" : "eileen.lowe"
+   },
+   "description" : null,
+   "updated_at" : "2016-01-07T12:44:33.959Z",
+   "milestone" : null
+}
+```
 
 ## Edit issue
 
-Updates an existing project issue. This function is also used to mark an issue as closed.
+Updates an existing project issue. This call is also used to mark an issue as
+closed.
+
+If the operation is successful, a code of `200` and the updated issue is
+returned. If an error occurs, an error number and a message explaining the
+reason is returned.
 
 ```
 PUT /projects/:id/issues/:issue_id
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`            | integer | yes | The ID of a project |
+| `issue_id`      | integer | yes | The ID of a project's issue |
+| `title`         | string  | no  | The title of an issue |
+| `description`   | string  | no  | The description of an issue  |
+| `assignee_id`   | integer | no  | The ID of a user to assign the issue to |
+| `milestone_id`  | integer | no  | The ID of a milestone to assign the issue to |
+| `labels`        | string  | no  | Comma-separated label names for an issue  |
+| `state_event`   | string  | no  | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
+
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close
+```
 
-- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of a project's issue
-- `title` (optional) - The title of an issue
-- `description` (optional) - The description of an issue
-- `assignee_id` (optional) - The ID of a user to assign issue
-- `milestone_id` (optional) - The ID of a milestone to assign issue
-- `labels` (optional) - Comma-separated label names for an issue
-- `state_event` (optional) - The state event of an issue ('close' to close issue and 'reopen' to reopen it)
+Example response:
 
-If the operation is successful, 200 and the updated issue is returned.
-If an error occurs, an error number and a message explaining the reason is returned.
+```json
+{
+   "created_at" : "2016-01-07T12:46:01.410Z",
+   "author" : {
+      "name" : "Alexandra Bashirian",
+      "avatar_url" : null,
+      "username" : "eileen.lowe",
+      "id" : 18,
+      "state" : "active",
+      "web_url" : "https://gitlab.example.com/u/eileen.lowe"
+   },
+   "state" : "closed",
+   "title" : "Issues with auth",
+   "project_id" : 4,
+   "description" : null,
+   "updated_at" : "2016-01-07T12:55:16.213Z",
+   "iid" : 15,
+   "labels" : [
+      "bug"
+   ],
+   "id" : 85,
+   "assignee" : null,
+   "milestone" : null
+}
+```
 
 ## Delete existing issue (**Deprecated**)
 
-The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `state_event` set to `close`.
+This call is deprecated and returns a `405 Method Not Allowed` error if called.
+An issue gets now closed and is done by calling
+`PUT /projects/:id/issues/:issue_id` with the parameter `state_event` set to
+`close`. See [edit issue](#edit-issue) for more details.
 
 ```
 DELETE /projects/:id/issues/:issue_id
 ```
 
-Parameters:
-
-- `id` (required) - The project ID
-- `issue_id` (required) - The ID of the issue
-
 ## Comments on issues
 
-Comments are done via the notes resource.
+Comments are done via the [notes](notes.md) resource.
diff --git a/doc/api/labels.md b/doc/api/labels.md
index de41f35d284ee86d45d3ae62483fea6da3e500ec..6496ffe9fd1f315fd19c3eeea64a1edf766a240f 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -2,83 +2,153 @@
 
 ## List labels
 
-Get all labels for given project.
+Get all labels for a given project.
 
 ```
 GET /projects/:id/labels
 ```
 
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/labels
+```
+
+Example response:
+
 ```json
 [
-    {
-        "name": "Awesome",
-        "color": "#DD10AA"
-    },
-    {
-        "name": "Documentation",
-        "color": "#1E80DD"
-    },
-    {
-        "name": "Feature",
-        "color": "#11FF22"
-    },
-    {
-        "name": "Bug",
-        "color": "#EE1122"
-    }
+   {
+      "name" : "bug",
+      "color" : "#d9534f"
+   },
+   {
+      "color" : "#d9534f",
+      "name" : "confirmed"
+   },
+   {
+      "name" : "critical",
+      "color" : "#d9534f"
+   },
+   {
+      "color" : "#428bca",
+      "name" : "discussion"
+   },
+   {
+      "name" : "documentation",
+      "color" : "#f0ad4e"
+   },
+   {
+      "color" : "#5cb85c",
+      "name" : "enhancement"
+   },
+   {
+      "color" : "#428bca",
+      "name" : "suggestion"
+   },
+   {
+      "color" : "#f0ad4e",
+      "name" : "support"
+   }
 ]
 ```
 
 ## Create a new label
 
-Creates a new label for given repository with given name and color.
+Creates a new label for the given repository with the given name and color.
+
+It returns 200 if the label was successfully created, 400 for wrong parameters
+and 409 if the label already exists.
 
 ```
 POST /projects/:id/labels
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer | yes | The ID of the project |
+| `name`    | string  | yes | The name of the label |
+| `color`   | string  | yes | The color of the label in 6-digit hex notation with leading `#` sign |
+
+```bash
+curl --data "name=feature&color=#5843AD" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
+```
 
-- `id` (required) - The ID of a project
-- `name` (required) - The name of the label
-- `color` (required) -  Color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)
+Example response:
 
-It returns 200 and the newly created label, if the operation succeeds.
-If the label already exists, 409 and an error message is returned.
-If label parameters are invalid, 400 and an explaining error message is returned.
+```json
+{
+   "name" : "feature",
+   "color" : "#5843AD"
+}
+```
 
 ## Delete a label
 
-Deletes a label given by its name.
+Deletes a label with a given name.
+
+It returns 200 if the label was successfully deleted, 400 for wrong parameters
+and 404 if the label does not exist.
+In case of an error, an additional error message is returned.
 
 ```
 DELETE /projects/:id/labels
 ```
 
-- `id` (required) - The ID of a project
-- `name` (required) - The name of the label to be deleted
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer | yes | The ID of the project |
+| `name`    | string  | yes | The name of the label |
 
-It returns 200 if the label successfully was deleted, 400 for wrong parameters
-and 404 if the label does not exist.
-In case of an error, additionally an error message is returned.
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels?name=bug"
+```
+
+Example response:
+
+```json
+{
+   "title" : "feature",
+   "color" : "#5843AD",
+   "updated_at" : "2015-11-03T21:22:30.737Z",
+   "template" : false,
+   "project_id" : 1,
+   "created_at" : "2015-11-03T21:22:30.737Z",
+   "id" : 9
+}
+```
 
 ## Edit an existing label
 
-Updates an existing label with new name or now color. At least one parameter
+Updates an existing label with new name or new color. At least one parameter
 is required, to update the label.
 
+It returns 200 if the label was successfully deleted, 400 for wrong parameters
+and 404 if the label does not exist.
+In case of an error, an additional error message is returned.
+
 ```
 PUT /projects/:id/labels
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer | yes | The ID of the project |
+| `name`    | string  | yes | The name of the existing label |
+| `new_name` | string  | yes if `color` if not provided | The new name of the label |
+| `color`   | string  | yes if `new_name` is not provided | The new color of the label in 6-digit hex notation with leading `#` sign |
+
+```bash
+curl -X PUT --data "name=documentation&new_name=docs&color=#8E44AD" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
+```
 
-- `id` (required) - The ID of a project
-- `name` (required) - The name of the existing label
-- `new_name` (optional) - The new name of the label
-- `color` (optional) -  New color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)
+Example response:
 
-On success, this method returns 200 with the updated label.
-If required parameters are missing or parameters are invalid, 400 is returned.
-If the label to be updated is missing, 404 is returned.
-In case of an error, additionally an error message is returned.
+```json
+{
+   "color" : "#8E44AD",
+   "name" : "docs"
+}
+```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 8bc0a67067a1a22685a7cbf8bac444b73d0c9201..85ed31320b9e57a43b8fc15f2620510a59213ae2 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -60,7 +60,7 @@ Parameters:
 Shows information about a single merge request.
 
 ```
-GET /projects/:id/merge_request/:merge_request_id
+GET /projects/:id/merge_requests/:merge_request_id
 ```
 
 Parameters:
@@ -105,7 +105,7 @@ Parameters:
 Get a list of merge request commits.
 
 ```
-GET /projects/:id/merge_request/:merge_request_id/commits
+GET /projects/:id/merge_requests/:merge_request_id/commits
 ```
 
 Parameters:
@@ -142,7 +142,7 @@ Parameters:
 Shows information about the merge request including its files and changes.
 
 ```
-GET /projects/:id/merge_request/:merge_request_id/changes
+GET /projects/:id/merge_requests/:merge_request_id/changes
 ```
 
 Parameters:
@@ -264,7 +264,7 @@ If an error occurs, an error number and a message explaining the reason is retur
 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
+PUT /projects/:id/merge_requests/:merge_request_id
 ```
 
 Parameters:
@@ -323,7 +323,7 @@ If merge request is already merged or closed - you get 405 and error message 'Me
 If you don't have permissions to accept this merge request - you'll get a 401
 
 ```
-PUT /projects/:id/merge_request/:merge_request_id/merge
+PUT /projects/:id/merge_requests/:merge_request_id/merge
 ```
 
 Parameters:
@@ -373,7 +373,7 @@ If the merge request is already merged or closed - you get 405 and error message
 
 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
+PUT /projects/:id/merge_requests/:merge_request_id/cancel_merge_when_build_succeeds
 ```
 Parameters:
 
@@ -409,66 +409,6 @@ Parameters:
 }
 ```
 
-## Post comment to MR
-
-Adds a comment to a merge request.
-
-```
-POST /projects/:id/merge_request/:merge_request_id/comments
-```
-
-Parameters:
-
-- `id` (required)               - The ID of a project
-- `merge_request_id` (required) - ID of merge request
-- `note` (required)             - Text of comment
-
-```json
-{
-  "note": "text1"
-}
-```
-
-## Get the comments on a MR
-
-Gets all the comments associated with a merge request.
-
-```
-GET /projects/:id/merge_request/:merge_request_id/comments
-```
-
-Parameters:
-
-- `id` (required)               - The ID of a project
-- `merge_request_id` (required) - ID of merge request
-
-```json
-[
-  {
-    "note": "this is the 1st comment on the 2merge merge request",
-    "author": {
-      "id": 11,
-      "username": "admin",
-      "email": "admin@example.com",
-      "name": "Administrator",
-      "state": "active",
-      "created_at": "2014-03-06T08:17:35.000Z"
-    }
-  },
-  {
-    "note": "Status changed to closed",
-    "author": {
-      "id": 11,
-      "username": "admin",
-      "email": "admin@example.com",
-      "name": "Administrator",
-      "state": "active",
-      "created_at": "2014-03-06T08:17:35.000Z"
-    }
-  }
-]
-```
-
 ## Comments on merge requets
 
-Comments are done via the notes resource.
+Comments are done via the [notes](notes.md) resource.
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index 7b3238441f6e5ed7f2327cecb07955eca1e73867..42d9ce3d3915d866ece9f536d38cdb9ab55e36a8 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -1,13 +1,29 @@
 # Namespaces
 
+Usernames and groupnames fall under a special category called namespaces.
+
+For users and groups supported API calls see the [users](users.md) and
+[groups](groups.md) documentation respectively.
+
+[Pagination](README.md#pagination) is used.
+
 ## List namespaces
 
-Get a list of namespaces. (As user: my namespaces, as admin: all namespaces)
+Get a list of the namespaces of the authenticated user. If the user is an
+administrator, a list of all namespaces in the GitLab instance is shown.
 
 ```
 GET /namespaces
 ```
 
+Example request:
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces
+```
+
+Example response:
+
 ```json
 [
   {
@@ -23,22 +39,32 @@ GET /namespaces
 ]
 ```
 
-You can search for namespaces by name or path, see below.
-
 ## Search for namespace
 
-Get all namespaces that match your string in their name or path.
+Get all namespaces that match a string in their name or path.
 
 ```
 GET /namespaces?search=foobar
 ```
 
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `search`  | string | no | Returns a list of namespaces the user is authorized to see based on the search criteria |
+
+Example request:
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces?search=twitter
+```
+
+Example response:
+
 ```json
 [
   {
-    "id": 1,
-    "path": "user1",
-    "kind": "user"
+    "id": 4,
+    "path": "twitter",
+    "kind": "group"
   }
 ]
 ```
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 96867c679154a865f31cd2721572232eea6eb4f5..001de76c7af7a27e554996bad3fffdf00c5b5698 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -1,67 +1,77 @@
 # Application settings
 
-This API allows you to read and modify GitLab instance application settings. 
+These API calls allow you to read and modify GitLab instance application
+settings as appear in `/admin/application_settings`. You have to be an
+administrator in order to perform this action.
 
+## Get current application settings
 
-## Get current application settings: 
+List the current application settings of the GitLab instance.
 
 ```
 GET /application/settings
 ```
 
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings
+```
+
+Example response:
+
 ```json
 {
-  "id": 1,
-  "default_projects_limit": 10,
-  "signup_enabled": true,
-  "signin_enabled": true,
-  "gravatar_enabled": true,
-  "sign_in_text": "",
-  "created_at": "2015-06-12T15:51:55.432Z",
-  "updated_at": "2015-06-30T13:22:42.210Z",
-  "home_page_url": "",
-  "default_branch_protection": 2,
-  "twitter_sharing_enabled": true,
-  "restricted_visibility_levels": [],
-  "max_attachment_size": 10,
-  "session_expire_delay": 10080,
-  "default_project_visibility": 0,
-  "default_snippet_visibility": 0,
-  "restricted_signup_domains": [],
-  "user_oauth_applications": true,
-  "after_sign_out_path": ""
+   "default_projects_limit" : 10,
+   "signup_enabled" : true,
+   "id" : 1,
+   "default_branch_protection" : 2,
+   "restricted_visibility_levels" : [],
+   "signin_enabled" : true,
+   "twitter_sharing_enabled" : true,
+   "after_sign_out_path" : null,
+   "max_attachment_size" : 10,
+   "user_oauth_applications" : true,
+   "updated_at" : "2016-01-04T15:44:55.176Z",
+   "session_expire_delay" : 10080,
+   "home_page_url" : null,
+   "default_snippet_visibility" : 0,
+   "restricted_signup_domains" : [],
+   "created_at" : "2016-01-04T15:44:55.176Z",
+   "default_project_visibility" : 0,
+   "gravatar_enabled" : true,
+   "sign_in_text" : null
 }
 ```
 
-## Change application settings: 
-
-
+## Change application settings
 
 ```
 PUT /application/settings
 ```
 
-Parameters:
-
-- `default_projects_limit` - project limit per user
-- `signup_enabled` - enable registration
-- `signin_enabled` - enable login via GitLab account
-- `gravatar_enabled` - enable gravatar
-- `sign_in_text` - text on login page
-- `home_page_url` - redirect to this URL when not logged in
-- `default_branch_protection` - determine if developers can push to master
-- `twitter_sharing_enabled` - allow users to share project creation in twitter
-- `restricted_visibility_levels` - restrict certain visibility levels
-- `max_attachment_size` - limit attachment size
-- `session_expire_delay` - session lifetime
-- `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 applications
-- `after_sign_out_path` - where redirect user after logout
+| Attribute | Type | Required | Description |
+| --------- | ---- | :------: | ----------- |
+| `default_projects_limit` | integer  | no | Project limit per user. Default is `10` |
+| `signup_enabled`    | boolean | no  | Enable registration. Default is `true`. |
+| `signin_enabled`    | boolean | no  | Enable login via a GitLab account. Default is `true`. |
+| `gravatar_enabled`  | boolean | no  | Enable Gravatar |
+| `sign_in_text`      | string  | no  | Text on login page |
+| `home_page_url`     | string  | no  | Redirect to this URL when not logged in |
+| `default_branch_protection` | integer | no | Determine if developers can push to master. Can take `0` _(not protected, both developers and masters can push new commits, force push or delete the branch)_, `1` _(partially protected, developers can push new commits, but cannot force push or delete the branch, masters can do anything)_ or `2` _(fully protected, developers cannot push new commits, force push or delete the branch, masters can do anything)_ as a parameter. Default is `1`. |
+| `twitter_sharing_enabled` | boolean | no | Allow users to share project creation on Twitter |
+| `restricted_visibility_levels` | array of integers | no | Selected levels cannot be used by non-admin users for projects or snippets. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is null which means there is no restriction. |
+| `max_attachment_size` | integer | no | Limit attachment size in MB |
+| `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes |
+| `default_project_visibility` | integer | no | What visibility level new projects receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.|
+| `default_snippet_visibility` | integer | no | What visibility level new snippets receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.|
+| `restricted_signup_domains` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. |
+| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
+| `after_sign_out_path` | string | no | Where to redirect users after logout |
 
-All parameters are optional. You can send only one that you want to change.
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1
+```
 
+Example response:
 
 ```json
 {
@@ -79,7 +89,7 @@ All parameters are optional. You can send only one that you want to change.
   "restricted_visibility_levels": [],
   "max_attachment_size": 10,
   "session_expire_delay": 10080,
-  "default_project_visibility": 0,
+  "default_project_visibility": 1,
   "default_snippet_visibility": 0,
   "restricted_signup_domains": [],
   "user_oauth_applications": true,
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index f9637d8a6c4c18bd8906897dd237bc3cf82b6289..dc036d7e27fce417c1cc27dab18f620e146cfee6 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -1,40 +1,71 @@
 # System hooks
 
-All methods require admin authorization.
+All methods require administrator authorization.
 
-The URL endpoint of the system hooks can be configured in [the admin area under hooks](/admin/hooks).
+The URL endpoint of the system hooks can also be configured using the UI in
+the admin area under **Hooks** (`/admin/hooks`).
+
+Read more about [system hooks](../system_hooks/system_hooks.md).
 
 ## List system hooks
 
-Get list of system hooks
+Get a list of all system hooks.
+
+---
 
 ```
 GET /hooks
 ```
 
-Parameters:
+Example request:
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks
+```
 
-- **none**
+Example response:
 
 ```json
 [
-  {
-    "id": 3,
-    "url": "http://example.com/hook",
-    "created_at": "2013-10-02T10:15:31Z"
-  }
+   {
+      "id" : 1,
+      "url" : "https://gitlab.example.com/hook",
+      "created_at" : "2015-11-04T20:07:35.874Z"
+   }
 ]
 ```
 
-## Add new system hook hook
+## Add new system hook
+
+Add a new system hook.
+
+---
 
 ```
 POST /hooks
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `url` | string | yes | The hook URL |
+
+Example request:
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/hooks?url=https://gitlab.example.com/hook"
+```
+
+Example response:
 
-- `url` (required) - The hook URL
+```json
+[
+   {
+      "id" : 2,
+      "url" : "https://gitlab.example.com/hook",
+      "created_at" : "2015-11-04T20:07:35.874Z"
+   }
+]
+```
 
 ## Test system hook
 
@@ -42,29 +73,68 @@ Parameters:
 GET /hooks/:id
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the hook |
 
-- `id` (required) - The ID of hook
+Example request:
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2
+```
+
+Example response:
 
 ```json
 {
-  "event_name": "project_create",
-  "name": "Ruby",
-  "path": "ruby",
-  "project_id": 1,
-  "owner_name": "Someone",
-  "owner_email": "example@gitlabhq.com"
+   "project_id" : 1,
+   "owner_email" : "example@gitlabhq.com",
+   "owner_name" : "Someone",
+   "name" : "Ruby",
+   "path" : "ruby",
+   "event_name" : "project_create"
 }
 ```
 
 ## Delete system hook
 
-Deletes a system hook. This is an idempotent API function and returns `200 OK` even if the hook is not available. If the hook is deleted it is also returned as JSON.
+Deletes a system hook. This is an idempotent API function and returns `200 OK`
+even if the hook is not available.
+
+If the hook is deleted, a JSON object is returned. An error is raised if the
+hook is not found.
+
+---
 
 ```
 DELETE /hooks/:id
 ```
 
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the hook |
+
+Example request:
 
-- `id` (required) - The ID of hook
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2
+```
+
+Example response:
+
+```json
+{
+   "note_events" : false,
+   "project_id" : null,
+   "enable_ssl_verification" : true,
+   "url" : "https://gitlab.example.com/hook",
+   "updated_at" : "2015-11-04T20:12:15.931Z",
+   "issues_events" : false,
+   "merge_requests_events" : false,
+   "created_at" : "2015-11-04T20:12:15.931Z",
+   "service_id" : null,
+   "id" : 2,
+   "push_events" : true,
+   "tag_push_events" : false
+}
+```
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 4cdd2e1ad3394017887160e365557e2f7028a528..5886829be51084594c0637561b464f89d2866e3c 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -12,6 +12,7 @@
 * [Using Variables](variables/README.md)
 * [Using SSH keys](ssh_keys/README.md)
 * [Triggering builds through the API](triggers/README.md)
+* [Build artifacts](build_artifacts/README.md)
 
 ### Languages
 
diff --git a/doc/ci/api/commits.md b/doc/ci/api/commits.md
index 4df7afc6c529e916ce37b3e2b30cf6f7685e1daf..871de7abcce18ccd03451140cca3b49c40aa08c5 100644
--- a/doc/ci/api/commits.md
+++ b/doc/ci/api/commits.md
@@ -1,5 +1,12 @@
 # Commits API
 
+**DEPRECATED**
+
+Since GitLab 8.1, there is a new commit status API. Please see the [revised
+documentation](../../api/commits.md#commit-status).
+
+---
+
 __Authentication is done by GitLab CI project token__
 
 ## Commits
diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..71db5aa5dc86f9cc28c93e8975c34538be38b24d
--- /dev/null
+++ b/doc/ci/build_artifacts/README.md
@@ -0,0 +1,176 @@
+# Introduction to build artifacts
+
+Artifacts is a list of files and directories which are attached to a build
+after it completes successfully.
+
+Since GitLab 8.2 and [GitLab Runner] 0.7.0, build artifacts that are created by
+GitLab Runner are uploaded to GitLab and are downloadable as a single archive
+(`tar.gz`) using the GitLab UI.
+
+Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
+changed to `ZIP`, and it is now possible to browse its contents, with the added
+ability of downloading the files separately.
+
+**Note:**
+The artifacts browser will be available only for new artifacts that are sent
+to GitLab using GitLab Runner version 1.0 and up. It will not be possible to
+browse old artifacts already uploaded to GitLab.
+
+## Enabling build artifacts
+
+_If you are searching for ways to use artifacts, jump to
+[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._
+
+The artifacts feature is enabled by default in all GitLab installations.
+To disable it site-wide, follow the steps below.
+
+---
+
+**In Omnibus installations:**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
+
+    ```ruby
+    gitlab_rails['artifacts_enabled'] = false
+    ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**In installations from source:**
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
+
+    ```yaml
+    artifacts:
+      enabled: false
+    ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
+## Defining artifacts in `.gitlab-ci.yml`
+
+A simple example of using the artifacts definition in `.gitlab-ci.yml` would be
+the following:
+
+```yaml
+pdf:
+  script: xelatex mycv.tex
+  artifacts:
+    paths:
+    - mycv.pdf
+```
+
+A job named `pdf` calls the `xelatex` command in order to build a pdf file from
+the latex source file `mycv.tex`. We then define the `artifacts` paths which in
+turn are defined with the `paths` keyword. All paths to files and directories
+are relative to the repository that was cloned during the build.
+
+For more examples on artifacts, follow the
+[separate artifacts yaml documentation](../yaml/README.md#artifacts).
+
+## Storing build artifacts
+
+After a successful build, GitLab Runner uploads an archive containing the build
+artifacts to GitLab.
+
+To change the location where the artifacts are stored, follow the steps below.
+
+---
+
+**In Omnibus installations:**
+
+_The artifacts are stored by default in
+`/var/opt/gitlab/gitlab-rails/shared/artifacts`._
+
+1. To change the storage path for example to `/mnt/storage/artifacts`, edit
+   `/etc/gitlab/gitlab.rb` and add the following line:
+
+    ```ruby
+    gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts"
+    ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**In installations from source:**
+
+_The artifacts are stored by default in
+`/home/git/gitlab/shared/artifacts`._
+
+1. To change the storage path for example to `/mnt/storage/artifacts`, edit
+   `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
+
+    ```yaml
+    artifacts:
+      enabled: true
+      path: /mnt/storage/artifacts
+    ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
+## Browsing build artifacts
+
+When GitLab receives an artifacts archive, an archive metadata file is also
+generated. This metadata file describes all the entries that are located in the
+artifacts archive itself. The metadata file is in a binary format, with
+additional GZIP compression.
+
+GitLab does not extract the artifacts archive in order to save space, memory
+and disk I/O. It instead inspects the metadata file which contains all the
+relevant information. This is especially important when there is a lot of
+artifacts, or an archive is a very large file.
+
+---
+
+After a successful build, if you visit the build's specific page, you can see
+that there are two buttons.
+
+One is for downloading the artifacts archive and the other for browsing its
+contents.
+
+![Build artifacts browser button](img/build_artifacts_browser_button.png)
+
+---
+
+The archive browser shows the name and the actual file size of each file in the
+archive. If your artifacts contained directories, then you are also able to
+browse inside them.
+
+Below you can see an image of three different file formats, as well as two
+directories.
+
+![Build artifacts browser](img/build_artifacts_browser.png)
+
+---
+
+## Downloading build artifacts
+
+If you need to download the whole archive, there are buttons in various places
+inside GitLab that make that possible.
+
+1. While on the builds page, you can see the download icon for each build's
+   artifacts archive in the right corner
+
+1. While inside a specific build, you are presented with a download button
+   along with the one that browses the archive
+
+1. And finally, when browsing an archive you can see the download button at
+   the top right corner
+
+---
+
+Note that GitLab does not extract the entire artifacts archive to send just a
+single file to the user.
+
+When clicking on a specific file, [GitLab Workhorse] extracts it from the
+archive and the download begins.
+
+This implementation saves space, memory and disk I/O.
+
+[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner "GitLab Runner repository"
+[reconfigure gitlab]: ../../administration/restart_gitlab.md "How to restart GitLab documentation"
+[restart gitlab]: ../../administration/restart_gitlab.md "How to restart GitLab documentation"
+[gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository"
diff --git a/doc/ci/build_artifacts/img/build_artifacts_browser.png b/doc/ci/build_artifacts/img/build_artifacts_browser.png
new file mode 100644
index 0000000000000000000000000000000000000000..73ed4eeb92781c798c3461283ca84d34d462c61c
Binary files /dev/null and b/doc/ci/build_artifacts/img/build_artifacts_browser.png differ
diff --git a/doc/ci/build_artifacts/img/build_artifacts_browser_button.png b/doc/ci/build_artifacts/img/build_artifacts_browser_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..f5d15bc3e7d1bf1e5db53efb2e4ef7e771576a33
Binary files /dev/null and b/doc/ci/build_artifacts/img/build_artifacts_browser_button.png differ
diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
index e52e1547461d974306cc0393a053f86685dcdbe9..c1bb47e4291bc216d9bd01e5349bfa7b444f0ba4 100644
--- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
@@ -56,12 +56,12 @@ gitlab-ci-multi-runner register \
   --non-interactive \
   --url "https://gitlab.com/ci/" \
   --registration-token "PROJECT_REGISTRATION_TOKEN" \
-  --description "ruby-2.1" \
+  --description "ruby-2.2" \
   --executor "docker" \
-  --docker-image ruby:2.1 \
+  --docker-image ruby:2.2 \
   --docker-postgres latest
 ```
 
-With the command above, you create a runner that uses [ruby:2.1](https://registry.hub.docker.com/u/library/ruby/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database.
+With the command above, you create a runner that uses [ruby:2.2](https://registry.hub.docker.com/u/library/ruby/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database.
 
 To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password.
diff --git a/doc/ci/languages/php.md b/doc/ci/languages/php.md
index dacb67fa3ff4cfaf209fe96c0161b13d13730a91..77f9fae5bb68a8be45a6ae0b959e2dbf72ad6ecc 100644
--- a/doc/ci/languages/php.md
+++ b/doc/ci/languages/php.md
@@ -12,7 +12,7 @@ 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
+However, not everything is plug 'n' play, you still need to configure some
 things manually.
 
 As with every build, you need to create a valid `.gitlab-ci.yml` describing the
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index b99ea25a3fe33ef74ceef3a9a4202452e897e95e..862cacda586bb21fb56a49cd5c93261295799921 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -56,7 +56,7 @@ export CI_SERVER_VERSION=""
 ```
 
 ### YAML-defined variables
-**This feature requires GitLab Runner 0.5.0 or higher**
+**This feature requires GitLab Runner 0.5.0 or higher and GitLab CI 7.14 or higher **
 
 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.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index fd0d49de4e438ba654ee160276733cb38768069d..4d280297dbbf7fe38a3b69ad6968d2723aae7187 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -33,7 +33,7 @@ The YAML syntax allows for using more complex job specifications than in the
 above example:
 
 ```yaml
-image: ruby:2.1
+image: ruby:2.2
 services:
   - postgres
 
@@ -135,10 +135,9 @@ thus allowing to fine tune them.
 ### cache
 
 `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.
+cached between builds.
+
+**By default the caching is enabled per-job and per-branch.**
 
 If `cache` is defined outside the scope of the jobs, it means it is set
 globally and all jobs will use its definition.
@@ -152,6 +151,64 @@ cache:
   - binaries/
 ```
 
+#### cache:key
+
+_**Note:** Introduced in GitLab Runner v1.0.0._
+
+The `key` directive allows you to define the affinity of caching
+between jobs, allowing to have a single cache for all jobs,
+cache per-job, cache per-branch or any other way you deem proper.
+
+This allows you to fine tune caching, allowing you to cache data between
+different jobs or even different branches.
+
+The `cache:key` variable can use any of the [predefined variables](../variables/README.md).
+
+---
+
+**Example configurations**
+
+To enable per-job caching:
+
+```yaml
+cache:
+  key: "$CI_BUILD_NAME"
+  untracked: true
+```
+
+To enable per-branch caching:
+
+```yaml
+cache:
+  key: "$CI_BUILD_REF_NAME"
+  untracked: true
+```
+
+To enable per-job and per-branch caching:
+
+```yaml
+cache:
+  key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME"
+  untracked: true
+```
+
+To enable per-branch and per-stage caching:
+
+```yaml
+cache:
+  key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
+  untracked: true
+```
+
+If you use **Windows Batch** to run your shell scripts you need to replace
+`$` with `%`:
+
+```yaml
+cache:
+  key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%"
+  untracked: true
+```
+
 ## Jobs
 
 `.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md
index f9b488681820db5041dc5bd1678697eaf0ac981d..05db30b4a7e02f2dfa4fa29f7f3a1c182bc9c8a0 100644
--- a/doc/development/ci_setup.md
+++ b/doc/development/ci_setup.md
@@ -26,7 +26,7 @@ We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ci/blob/master
 # Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
 
 - Language: Ruby
-- Ruby version: 2.1.2
+- Ruby version: 2.2.4
 - database.yml: pg
 
 Build commands
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 0bd32b7820169e87fe7dd632a63f7958f5fcb8ac..caaa4032db26ff7fd1e562fb886655c5f6f650b5 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -103,6 +103,23 @@ Inside the document:
   `_**Note:** This feature was introduced in GitLab EE 8.3_`. Otherwise, leave
   this mention out
 
+## References
+
+- **GitLab Restart:**
+  There are many cases that a restart/reconfigure of GitLab is required. To
+  avoid duplication, link to the special document that can be found in
+  [`doc/administration/restart_gitlab.md`][doc-restart]. Usually the text will
+  read like:
+
+    ```
+    Save the file and [reconfigure GitLab](../administration/restart_gitlab.md)
+    for the changes to take effect.
+    ```
+  If the document you are editing resides in a place other than the GitLab CE/EE
+  `doc/` directory, instead of the relative link, use the full path:
+  `http://doc.gitlab.com/ce/administration/restart_gitlab.html`.
+  Replace `reconfigure` with `restart` where appropriate.
+
 ## API
 
 Here is a list of must-have items. Use them in the exact order that appears
@@ -229,3 +246,4 @@ curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "restricted_signup_domai
 [cURL]: http://curl.haxx.se/ "cURL website"
 [single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html
 [gfm]: http://doc.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation"
+[doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation"
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 00030729a4b5e78cfbe6c86e3274493506f8d69c..2cc2dbef41bb31de00297cfb0d213236e1846ba6 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -107,18 +107,25 @@ Then select 'Internet Site' and press enter to confirm the hostname.
 
 ## 2. Ruby
 
-The use of Ruby version managers such as [RVM](https://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby.
+_**Note:** The current supported Ruby versions are 2.1.x and 2.2.x. Ruby 2.3 is
+currently not supported._
 
-Remove the old Ruby 1.8 if present
+The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab
+in production, frequently leads to hard to diagnose problems. For example,
+GitLab Shell is called from OpenSSH, and having a version manager can prevent
+pushing and pulling over SSH. Version managers are not supported and we strongly
+advise everyone to follow the instructions below to use a system Ruby.
+
+Remove the old Ruby 1.8 if present:
 
     sudo apt-get remove ruby1.8
 
 Download Ruby and compile it:
 
     mkdir /tmp/ruby && cd /tmp/ruby
-    curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.7.tar.gz
-    echo 'e2e195a4a58133e3ad33b955c829bb536fa3c075  ruby-2.1.7.tar.gz' | shasum -c - && tar xzf ruby-2.1.7.tar.gz
-    cd ruby-2.1.7
+    curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.4.tar.gz
+    echo 'e2e195a4a58133e3ad33b955c829bb536fa3c075  ruby-2.2.4.tar.gz' | shasum -c - && tar xzf ruby-2.2.4.tar.gz
+    cd ruby-2.2.4
     ./configure --disable-install-rdoc
     make
     sudo make install
@@ -348,7 +355,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.5.4
+    sudo -u git -H git checkout 0.6.2
     sudo -u git -H make
 
 ### Initialize Database and Activate Advanced Features
@@ -555,3 +562,7 @@ this is likely due to an outdated Nginx or Apache configuration, or a missing or
 misconfigured gitlab-workhorse instance. Double-check that you've
 [installed Go](#3-go), [installed gitlab-workhorse](#install-gitlab-workhorse),
 and correctly [configured Nginx](#site-configuration).
+
+[RVM]: https://rvm.io/ "RVM Homepage"
+[rbenv]: https://github.com/sstephenson/rbenv "rbenv on GitHub"
+[chruby]: https://github.com/postmodern/chruby "chruby on GitHub"
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index c0ccdd374586e29ae5ec53939c5fde3b968c3df0..006dae8ca9a2df0fc0bcaf95afe0ac941fe0a946 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -32,15 +32,17 @@ Please consider using a virtual machine to run GitLab.
 
 ## Ruby versions
 
-GitLab requires Ruby (MRI) 2.1
+GitLab requires Ruby (MRI) 2.1.x or 2.2.x and currently does not work with version 2.3.
+
 You will have to use the standard MRI implementation of Ruby.
-We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab needs several Gems that have native extensions.
+We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
+needs several Gems that have native extensions.
 
 ## Hardware requirements
 
 ### Storage
 
-The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least as much free space as all your repos combined take up. 
+The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least as much free space as all your repos combined take up.
 
 If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
 
@@ -109,4 +111,4 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
 - Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
 - Safari 7+ (known problem: required fields in html5 do not work)
 - Opera (Latest released version)
-- Internet Explorer (IE) 10+ but please make sure that you have the `Compatibility View` mode disabled.
\ No newline at end of file
+- Internet Explorer (IE) 10+ but please make sure that you have the `Compatibility View` mode disabled.
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 5edac746c7b51cd47077a5890224e6db3b06ab0f..83116bc837060bd8569b2a3fddac6e16a2a31c86 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -5,10 +5,10 @@ trackers and external authentication.
 
 See the documentation below for details on how to configure these services.
 
-- [Jira](jira.md) Integrate with the JIRA issue tracker
+- [Jira](../project_services/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.
+- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd and Azure
 - [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
 - [CAS](cas.md) Configure GitLab to sign in using CAS
 - [Slack](slack.md) Integrate with the Slack chat service
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 3543a67dd49cf380cb4291e433b70ceb64ec12bf..a2d7e922aadc2b762426f96b373ac978213f09d8 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -19,7 +19,7 @@ To enable an external issue tracker you must configure the appropriate **Service
 Visit the links below for details:
 
 - [Redmine](../project_services/redmine.md)
-- [Jira](jira.md)
+- [Jira](../project_services/jira.md)
 
 ### Service Template
 
diff --git a/doc/integration/facebook.md b/doc/integration/facebook.md
index bc1f1673086d30f5475375a52990614bd9a48b90..77bb75cbfca7fd410f0a7f1a61c76f2e3833f58e 100644
--- a/doc/integration/facebook.md
+++ b/doc/integration/facebook.md
@@ -19,7 +19,7 @@ something else descriptive.
 
 1. Enter the address of your GitLab installation at the bottom of the package
 
-    ![Facebook Website URL](facebook_website_url.png)
+    ![Facebook Website URL](img/facebook_website_url.png)
 
 1. Choose "Next"
 
@@ -29,7 +29,7 @@ something else descriptive.
 
 1. Fill in a contact email for your app
 
-    ![Facebook App Settings](facebook_app_settings.png)
+    ![Facebook App Settings](img/facebook_app_settings.png)
 
 1. Choose "Save Changes"
 
@@ -45,7 +45,7 @@ something else descriptive.
 
 1. You should now see an app key and app secret (see screenshot). Keep this page open as you continue configuration.
 
-    ![Facebook API Keys](facebook_api_keys.png)
+    ![Facebook API Keys](img/facebook_api_keys.png)
 
 1.  On your GitLab server, open the configuration file.
 
diff --git a/doc/integration/github.md b/doc/integration/github.md
index a789d2c814f5b6a2d7c6a4f513f25f9a63d55b67..886784a27c9c0f6a9665be2375f12aa483e7e810 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -22,7 +22,7 @@ GitHub will generate an application ID and secret key for you to use.
 
 1.  You should now see a Client ID and Client Secret near the top right of the page (see screenshot). 
     Keep this page open as you continue configuration.
-    ![GitHub app](github_app.png)
+    ![GitHub app](img/github_app.png)
 
 1.  On your GitLab server, open the configuration file.
 
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
index 80e3c0142a07cdc56bbae01529679071d3bb7a6e..b215cc7c609a7cfc4473f9f1368106b9dec36f5f 100644
--- a/doc/integration/gitlab.md
+++ b/doc/integration/gitlab.md
@@ -28,7 +28,7 @@ GitLab.com will generate an application ID and secret key for you to use.
 
 1.  You should now see a Client ID and Client Secret near the top right of the page (see screenshot). 
     Keep this page open as you continue configuration. 
-    ![GitLab app](gitlab_app.png)
+    ![GitLab app](img/gitlab_app.png)
 
 1.  On your GitLab server, open the configuration file.
 
diff --git a/doc/integration/gmail_action_buttons_for_gitlab.md b/doc/integration/gmail_action_buttons_for_gitlab.md
index de45f25ad622aa4c1f245d854aa44ffb9520daab..05a91d9bef9b0c8c2e70558a146911fd3e033201 100644
--- a/doc/integration/gmail_action_buttons_for_gitlab.md
+++ b/doc/integration/gmail_action_buttons_for_gitlab.md
@@ -4,7 +4,7 @@ GitLab supports [Google actions in email](https://developers.google.com/gmail/ma
 
 If correctly setup, emails that require an action will be marked in Gmail.
 
-![gmail_actions_button.png](gmail_actions_button.png)
+![gmail_actions_button.png](img/gmail_action_buttons_for_gitlab.png)
 
 To get this functioning, you need to be registered with Google.
 [See how to register with Google in this document.](https://developers.google.com/gmail/markup/registering-with-google)
diff --git a/doc/integration/google.md b/doc/integration/google.md
index 91e9b2495cc603ddba294776f633cd855103b2dd..f9a20dd840d46f202f78720f4705745afe2bb0b0 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -25,7 +25,7 @@ To enable the Google OAuth2 OmniAuth provider you must register your application
     - Application type: "Web Application"
     - Authorized JavaScript origins: This isn't really used by GitLab but go ahead and put 'https://gitlab.example.com' here.
     - Authorized redirect URI: 'https://gitlab.example.com/users/auth/google_oauth2/callback'
-1. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. ![Google app](google_app.png)
+1. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. ![Google app](img/google_app.png)
 
 1.  On your GitLab server, open the configuration file.
 
diff --git a/doc/integration/facebook_api_keys.png b/doc/integration/img/facebook_api_keys.png
similarity index 100%
rename from doc/integration/facebook_api_keys.png
rename to doc/integration/img/facebook_api_keys.png
diff --git a/doc/integration/facebook_app_settings.png b/doc/integration/img/facebook_app_settings.png
similarity index 100%
rename from doc/integration/facebook_app_settings.png
rename to doc/integration/img/facebook_app_settings.png
diff --git a/doc/integration/facebook_website_url.png b/doc/integration/img/facebook_website_url.png
similarity index 100%
rename from doc/integration/facebook_website_url.png
rename to doc/integration/img/facebook_website_url.png
diff --git a/doc/integration/github_app.png b/doc/integration/img/github_app.png
similarity index 100%
rename from doc/integration/github_app.png
rename to doc/integration/img/github_app.png
diff --git a/doc/integration/gitlab_app.png b/doc/integration/img/gitlab_app.png
similarity index 100%
rename from doc/integration/gitlab_app.png
rename to doc/integration/img/gitlab_app.png
diff --git a/doc/integration/gmail_actions_button.png b/doc/integration/img/gmail_action_buttons_for_gitlab.png
similarity index 100%
rename from doc/integration/gmail_actions_button.png
rename to doc/integration/img/gmail_action_buttons_for_gitlab.png
diff --git a/doc/integration/google_app.png b/doc/integration/img/google_app.png
similarity index 100%
rename from doc/integration/google_app.png
rename to doc/integration/img/google_app.png
diff --git a/doc/integration/oauth_provider/admin_application.png b/doc/integration/img/oauth_provider_admin_application.png
similarity index 100%
rename from doc/integration/oauth_provider/admin_application.png
rename to doc/integration/img/oauth_provider_admin_application.png
diff --git a/doc/integration/oauth_provider/application_form.png b/doc/integration/img/oauth_provider_application_form.png
similarity index 100%
rename from doc/integration/oauth_provider/application_form.png
rename to doc/integration/img/oauth_provider_application_form.png
diff --git a/doc/integration/oauth_provider/authorized_application.png b/doc/integration/img/oauth_provider_authorized_application.png
similarity index 100%
rename from doc/integration/oauth_provider/authorized_application.png
rename to doc/integration/img/oauth_provider_authorized_application.png
diff --git a/doc/integration/oauth_provider/user_wide_applications.png b/doc/integration/img/oauth_provider_user_wide_applications.png
similarity index 100%
rename from doc/integration/oauth_provider/user_wide_applications.png
rename to doc/integration/img/oauth_provider_user_wide_applications.png
diff --git a/doc/integration/twitter_app_api_keys.png b/doc/integration/img/twitter_app_api_keys.png
similarity index 100%
rename from doc/integration/twitter_app_api_keys.png
rename to doc/integration/img/twitter_app_api_keys.png
diff --git a/doc/integration/twitter_app_details.png b/doc/integration/img/twitter_app_details.png
similarity index 100%
rename from doc/integration/twitter_app_details.png
rename to doc/integration/img/twitter_app_details.png
diff --git a/doc/integration/jira-integration-points.png b/doc/integration/jira-integration-points.png
deleted file mode 100644
index 0692a7b458a3eef7486c0c665b16eb4222cfd11a..0000000000000000000000000000000000000000
Binary files a/doc/integration/jira-integration-points.png and /dev/null differ
diff --git a/doc/integration/jira.md b/doc/integration/jira.md
index de574d5341090beac271a16ee7c04658203306bb..78aa66341161d9f9966355477460ecee98becfd5 100644
--- a/doc/integration/jira.md
+++ b/doc/integration/jira.md
@@ -1,149 +1,3 @@
-# GitLab Jira integration
+# 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, see the
-[configuration](#configuration) section. If you have one Jira instance you can
-pre-fill the settings page with a default template. To configure the template
-see the [Services Templates][services-templates] document.
-
-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](img/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, the 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](img/jira_merge_request_close.png)
-
----
-
-![The GitLab integration user leaves a comment on Jira](img/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
-
-JIRA configuration in GitLab is done via a project's **Services**.
-
-#### GitLab 7.8 and up with JIRA v6.x
-
-See next section.
-
-#### GitLab 7.8 and up
-
-_The currently supported JIRA versions are v6.x and v7.x._
-
-To enable JIRA integration in a project, navigate to the project's
-**Settings > Services > JIRA**.
-
-Fill in the required details on the page as described in the table below.
-
-| Field | Description |
-| ----- | ----------- |
-| `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](img/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.
-
-![Jira service page](img/jira_service_page.png)
-
----
-
-#### GitLab 6.x-7.7 with JIRA v6.x
-
-_**Note:** GitLab versions 7.8 and up contain various integration improvements.
-We strongly recommend upgrading._
-
-In `gitlab.yml` enable the JIRA issue tracker section by
-[uncommenting these lines][jira-gitlab-yml]. This will make sure that all
-issues within GitLab are pointing to the JIRA issue tracker.
-
-After you set this, you will be able to close issues in JIRA by a commit in
-GitLab.
-
-Go to your project's **Settings** page and fill in the project name for the
-JIRA project:
-
-![Set the JIRA project name in GitLab to 'NEW'](img/jira_project_name.png)
-
----
-
-You can also enable the JIRA service that will allow you to interact with JIRA
-issues. Go to the **Settings > Services > JIRA** and:
-
-1. Tick the active check box to enable the service
-1. Supply the URL to JIRA server, for example http://jira.example.com
-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 `2`
-1. Optional: supply the JIRA issue transition ID (issue transition to closed).
-   This is dependent on JIRA settings, default is `2`
-1. Hit save
-
-
-![Jira services page](img/jira_service.png)
-
-[services-templates]: ../project_services/services_templates.md
-[jira-gitlab-yml]: https://gitlab.com/subscribers/gitlab-ee/blob/6-8-stable-ee/config/gitlab.yml.example#L111-115
+This document was moved under [project_services/jira](../project_services/jira.md).
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
index 192c321f7129941e44c68b23e8257c36e8da4ee2..dbe5a175c82267ebeae4bc5ab83ea1ff3d07df49 100644
--- a/doc/integration/oauth_provider.md
+++ b/doc/integration/oauth_provider.md
@@ -15,16 +15,16 @@ GitLab has two ways to add new OAuth2 application to an instance, you can add ap
 ### Adding application through profile
 Go to your profile section 'Application' and press button 'New Application'
 
-![applications](oauth_provider/user_wide_applications.png)
+![applications](img/oauth_provider_user_wide_applications.png)
 
 After this you will see application form, where "Name" is arbitrary name, "Redirect URI" is URL in your app where users will be sent after authorization on GitLab.com.
 
-![application_form](oauth_provider/application_form.png)
+![application_form](img/oauth_provider_application_form.png)
 
 ### Authorized application
 Every application you authorized will be shown in your "Authorized application" sections.
 
-![authorized_application](oauth_provider/authorized_application.png)
+![authorized_application](img/oauth_provider_authorized_application.png)
 
 At any time you can revoke access just clicking button "Revoke"
 
@@ -32,4 +32,4 @@ At any time you can revoke access just clicking button "Revoke"
 
 If you want to create application that does not belong to certain user you can create it from admin area 
 
-![admin_application](oauth_provider/admin_application.png)
\ No newline at end of file
+![admin_application](img/oauth_provider_admin_application.png)
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index e9e17eb4165004a62c9c198e1894b218c76d5e61..8e6627b2be5bab315b6db92647fb7246f2b589e1 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -9,6 +9,23 @@ Configuring OmniAuth does not prevent standard GitLab authentication or LDAP (if
 - [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user)
 - [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login)
 
+## Supported Providers
+
+This is a list of the current supported OmniAuth providers. Before proceeding
+on each provider's documentation, make sure to first read this document as it
+contains some settings that are common for all providers.
+
+- [GitHub](github.md)
+- [Bitbucket](bitbucket.md)
+- [GitLab.com](gitlab.md)
+- [Google](google.md)
+- [Facebook](facebook.md)
+- [Twitter](twitter.md)
+- [Shibboleth](shibboleth.md)
+- [SAML](saml.md)
+- [Crowd](crowd.md)
+- [Azure](azure.md)
+
 ## Initial OmniAuth Configuration
 
 Before configuring individual OmniAuth providers there are a few global settings that are in common for all providers that we need to consider.
@@ -67,19 +84,6 @@ If you want to change these settings:
 
 Now we can choose one or more of the Supported Providers below to continue configuration.
 
-## Supported Providers
-
-- [GitHub](github.md)
-- [Bitbucket](bitbucket.md)
-- [GitLab.com](gitlab.md)
-- [Google](google.md)
-- [Facebook](facebook.md)
-- [Twitter](twitter.md)
-- [Shibboleth](shibboleth.md)
-- [SAML](saml.md)
-- [Crowd](crowd.md)
-- [Azure](azure.md)
-
 ## Enable OmniAuth for an Existing User
 
 Existing users can enable OmniAuth for specific providers after the account is created. For example, if the user originally signed in with LDAP an OmniAuth provider such as Twitter can be enabled. Follow the steps below to enable an OmniAuth provider for an existing user.
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index 84f1d74c058a158bd7ea9f2a770fc2b36601fe5a..ecbe0d3e8873beed79b21381dcc853260fc7ffa7 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -6,15 +6,17 @@ To enable Slack integration you must create an Incoming WebHooks integration on
 
 1.  [Sign in to Slack](https://slack.com/signin)
 
-1.  Select **Configure Integrations** from the dropdown next to your team name.
+1.  Select **Apps & Custom Integrations** from the dropdown next to your team name.
 
-1.  Select the **All Services** tab
+1.  Click the **Configure** link (right-upper corner).
 
-1.  Click **Add** next to Incoming Webhooks
+1.  Select the **Custom integrations** tab.
 
-1.  Pick Incoming WebHooks
+1.  Click the **Incoming WebHooks** row.
 
-1.  Choose the channel name you want to send notifications to
+1.  Click the **Add configuration** button.
+
+1.  Choose the channel name you want to send notifications to.
 
 1.  Click **Add Incoming WebHooks Integration**
     - Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**.
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index 52ed4a22339845e096b600cb55d7739ba864bbfa..4769f26b259ff5fdb668156f6ad8b96c85a2ba2c 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -14,7 +14,7 @@ To enable the Twitter OmniAuth provider you must register your application with
     - Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback'
     - Agree to the "Developer Agreement".
 
-    ![Twitter App Details](twitter_app_details.png)
+    ![Twitter App Details](img/twitter_app_details.png)
 1.  Select "Create your Twitter application."
 
 1.  Select the "Settings" tab.
@@ -27,7 +27,7 @@ To enable the Twitter OmniAuth provider you must register your application with
 
 1.  You should now see an API key and API secret (see screenshot). Keep this page open as you continue configuration.
 
-    ![Twitter app](twitter_app_api_keys.png)
+    ![Twitter app](img/twitter_app_api_keys.png)
 
 1.  On your GitLab server, open the configuration file.
 
@@ -76,4 +76,4 @@ To enable the Twitter OmniAuth provider you must register your application with
 
 1.  Restart GitLab for the changes to take effect.
 
-On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
\ No newline at end of file
+On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index bc8e7d155e7f761ff296f8617fd1b5e1457e4ec1..83c77742b19cbfb88e33f2f3701dcee0cf7752ae 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -88,6 +88,9 @@ GFM will autolink almost any URL you copy and paste into your text.
 
 ## Code and Syntax Highlighting
 
+_GitLab uses the [rouge ruby library][rouge] for syntax highlighting. For a
+list of supported languages visit the rouge website._
+
 Blocks of code are either fenced by lines with three back-ticks <code>```</code>, or are indented with four spaces. Only the fenced code blocks support syntax highlighting.
 
 ```no-highlight
@@ -585,3 +588,5 @@ By including colons in the header row, you can align the text within that column
 - This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
 - The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown.
 - [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown.
+
+[rouge]: http://rouge.jneen.net/ "Rouge website"
diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md
new file mode 100644
index 0000000000000000000000000000000000000000..b856e7935a3955df647f00abfed40fa1f4efc3aa
--- /dev/null
+++ b/doc/monitoring/performance/gitlab_configuration.md
@@ -0,0 +1,39 @@
+# GitLab Configuration
+
+GitLab Performance Monitoring is disabled by default. To enable it and change any of its
+settings, navigate to the Admin area in **Settings > Metrics**
+(`/admin/application_settings`).
+
+The minimum required settings you need to set are the InfluxDB host and port.
+Make sure _Enable InfluxDB Metrics_ is checked and hit **Save** to save the
+changes.
+
+---
+
+![GitLab Performance Monitoring Admin Settings](img/metrics_gitlab_configuration_settings.png)
+
+---
+
+Finally, a restart of all GitLab processes is required for the changes to take
+effect:
+
+```bash
+# For Omnibus installations
+sudo gitlab-ctl restart
+
+# For installations from source
+sudo service gitlab restart
+```
+
+## Pending Migrations
+
+When any migrations are pending, the metrics are disabled until the migrations
+have been performed.
+
+---
+
+Read more on:
+
+- [Introduction to GitLab Performance Monitoring](introduction.md)
+- [InfluxDB Configuration](influxdb_configuration.md)
+- [InfluxDB Schema](influxdb_schema.md)
diff --git a/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png b/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png
new file mode 100644
index 0000000000000000000000000000000000000000..14d82b6ac98c5c942b93a83d17f6f82987a5c7b3
Binary files /dev/null and b/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png differ
diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md
new file mode 100644
index 0000000000000000000000000000000000000000..3a2b598b78f5be80b8469adf0c4c6925a3d10b29
--- /dev/null
+++ b/doc/monitoring/performance/influxdb_configuration.md
@@ -0,0 +1,192 @@
+# InfluxDB Configuration
+
+The default settings provided by [InfluxDB] are not sufficient for a high traffic
+GitLab environment. The settings discussed in this document are based on the
+settings GitLab uses for GitLab.com, depending on your own needs you may need to
+further adjust them.
+
+If you are intending to run InfluxDB on the same server as GitLab, make sure
+you have plenty of RAM since InfluxDB can use quite a bit depending on traffic.
+
+Unless you are going with a budget setup, it's advised to run it separately.
+
+## Requirements
+
+- InfluxDB 0.9.5 or newer
+- A fairly modern version of Linux
+- At least 4GB of RAM
+- At least 10GB of storage for InfluxDB data
+
+Note that the RAM and storage requirements can differ greatly depending on the
+amount of data received/stored. To limit the amount of stored data users can
+look into [InfluxDB Retention Policies][influxdb-retention].
+
+## Installation
+
+Installing InfluxDB is out of the scope of this document. Please refer to the
+[InfluxDB documentation].
+
+## InfluxDB Server Settings
+
+Since InfluxDB has many settings that users may wish to customize themselves
+(e.g. what port to run InfluxDB on), we'll only cover the essentials.
+
+The configuration file in question is usually located at
+`/etc/influxdb/influxdb.conf`. Whenever you make a change in this file,
+InfluxDB needs to be restarted.
+
+### Storage Engine
+
+InfluxDB comes with different storage engines and as of InfluxDB 0.9.5 a new
+storage engine is available, called [TSM Tree]. All users **must** use the new
+`tsm1` storage engine as this [will be the default engine][tsm1-commit] in
+upcoming InfluxDB releases.
+
+Make sure you have the following in your configuration file:
+
+```
+[data]
+  dir = "/var/lib/influxdb/data"
+  engine = "tsm1"
+```
+
+### Admin Panel
+
+Production environments should have the InfluxDB admin panel **disabled**. This
+feature can be disabled by adding the following to your InfluxDB configuration
+file:
+
+```
+[admin]
+  enabled = false
+```
+
+### HTTP
+
+HTTP is required when using the [InfluxDB CLI] or other tools such as Grafana,
+thus it should be enabled. When enabling make sure to _also_ enable
+authentication:
+
+```
+[http]
+  enabled = true
+  auth-enabled = true
+```
+
+_**Note:** Before you enable authentication, you might want to [create an
+admin user](#create-a-new-admin-user)._
+
+### UDP
+
+GitLab writes data to InfluxDB via UDP and thus this must be enabled. Enabling
+UDP can be done using the following settings:
+
+```
+[[udp]]
+  enabled = true
+  bind-address = ":8089"
+  database = "gitlab"
+  batch-size = 1000
+  batch-pending = 5
+  batch-timeout = "1s"
+  read-buffer = 209715200
+```
+
+This does the following:
+
+1. Enable UDP and bind it to port 8089 for all addresses.
+2. Store any data received in the "gitlab" database.
+3. Define a batch of points to be 1000 points in size and allow a maximum of
+   5 batches _or_ flush them automatically after 1 second.
+4. Define a UDP read buffer size of 200 MB.
+
+One of the most important settings here is the UDP read buffer size as if this
+value is set too low, packets will be dropped. You must also make sure the OS
+buffer size is set to the same value, the default value is almost never enough.
+
+To set the OS buffer size to 200 MB, on Linux you can run the following command:
+
+```bash
+sysctl -w net.core.rmem_max=209715200
+```
+
+To make this permanent, add the following to `/etc/sysctl.conf` and restart the
+server:
+
+```bash
+net.core.rmem_max=209715200
+```
+
+It is **very important** to make sure the buffer sizes are large enough to
+handle all data sent to InfluxDB as otherwise you _will_ lose data. The above
+buffer sizes are based on the traffic for GitLab.com. Depending on the amount of
+traffic, users may be able to use a smaller buffer size, but we highly recommend
+using _at least_ 100 MB.
+
+When enabling UDP, users should take care to not expose the port to the public,
+as doing so will allow anybody to write data into your InfluxDB database (as
+[InfluxDB's UDP protocol][udp] doesn't support authentication). We recommend either
+whitelisting the allowed IP addresses/ranges, or setting up a VLAN and only
+allowing traffic from members of said VLAN.
+
+## Create a new admin user
+
+If you want to [enable authentication](#http), you might want to [create an
+admin user][influx-admin]:
+
+```
+influx -execute "CREATE USER jeff WITH PASSWORD '1234' WITH ALL PRIVILEGES"
+```
+
+## Create the `gitlab` database
+
+Once you get InfluxDB up and running, you need to create a database for GitLab.
+Make sure you have changed the [storage engine](#storage-engine) to `tsm1`
+before creating a database.
+
+_**Note:** If you [created an admin user](#create-a-new-admin-user) and enabled
+[HTTP authentication](#http), remember to append the username (`-username <username>`)
+and password (`-password <password>`)  you set earlier to the commands below._
+
+Run the following command to create a database named `gitlab`:
+
+```bash
+influx -execute 'CREATE DATABASE gitlab'
+```
+
+The name **must** be `gitlab`, do not use any other name.
+
+Next, make sure that the database was successfully created:
+
+```bash
+influx -execute 'SHOW DATABASES'
+```
+
+The output should be similar to:
+
+```
+name: databases
+---------------
+name
+_internal
+gitlab
+```
+
+That's it! Now your GitLab instance should send data to InfluxDB.
+
+---
+
+Read more on:
+
+- [Introduction to GitLab Performance Monitoring](introduction.md)
+- [GitLab Configuration](gitlab_configuration.md)
+- [InfluxDB Schema](influxdb_schema.md)
+
+[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management
+[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/
+[influxdb cli]: https://docs.influxdata.com/influxdb/v0.9/tools/shell/
+[udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/
+[influxdb]: https://influxdata.com/time-series-platform/influxdb/
+[tsm tree]: https://influxdata.com/blog/new-storage-engine-time-structured-merge-tree/
+[tsm1-commit]: https://github.com/influxdata/influxdb/commit/15d723dc77651bac83e09e2b1c94be480966cb0d
+[influx-admin]: https://docs.influxdata.com/influxdb/v0.9/administration/authentication_and_authorization/#create-a-new-admin-user
diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md
new file mode 100644
index 0000000000000000000000000000000000000000..a5a8aebd2d1381643cb27f0c1375a9e415bfa969
--- /dev/null
+++ b/doc/monitoring/performance/influxdb_schema.md
@@ -0,0 +1,87 @@
+# InfluxDB Schema
+
+The following measurements are currently stored in InfluxDB:
+
+- `PROCESS_file_descriptors`
+- `PROCESS_gc_statistics`
+- `PROCESS_memory_usage`
+- `PROCESS_method_calls`
+- `PROCESS_object_counts`
+- `PROCESS_transactions`
+- `PROCESS_views`
+
+Here, `PROCESS` is replaced with either `rails` or `sidekiq` depending on the
+process type. In all series, any form of duration is stored in milliseconds.
+
+## PROCESS_file_descriptors
+
+This measurement contains the number of open file descriptors over time. The
+value field `value` contains the number of descriptors.
+
+## PROCESS_gc_statistics
+
+This measurement contains Ruby garbage collection statistics such as the amount
+of minor/major GC runs (relative to the last sampling interval), the time spent
+in garbage collection cycles, and all fields/values returned by `GC.stat`.
+
+## PROCESS_memory_usage
+
+This measurement contains the process' memory usage (in bytes) over time. The
+value field `value` contains the number of bytes.
+
+## PROCESS_method_calls
+
+This measurement contains the methods called during a transaction along with
+their duration, and a name of the transaction action that invoked the method (if
+available). The method call duration is stored in the value field `duration`,
+while the method name is stored in the tag `method`. The tag `action` contains
+the full name of the transaction action. Both the `method` and `action` fields
+are in the following format:
+
+```
+ClassName#method_name
+```
+
+For example, a method called by the `show` method in the `UsersController` class
+would have `action` set to `UsersController#show`.
+
+## PROCESS_object_counts
+
+This measurement is used to store retained Ruby objects (per class) and the
+amount of retained objects. The number of objects is stored in the `count` value
+field while the class name is stored in the `type` tag.
+
+## PROCESS_transactions
+
+This measurement is used to store basic transaction details such as the time it
+took to complete a transaction, how much time was spent in SQL queries, etc. The
+following value fields are available:
+
+| Value | Description |
+| ----- | ----------- |
+| `duration`  | The total duration of the transaction |
+| `allocated_memory` | The amount of bytes allocated while the transaction was running. This value is only reliable when using single-threaded application servers |
+| `method_duration` | The total time spent in method calls |
+| `sql_duration` | The total time spent in SQL queries |
+| `view_duration` | The total time spent in views |
+
+## PROCESS_views
+
+This measurement is used to store view rendering timings for a transaction. The
+following value fields are available:
+
+| Value | Description |
+| ----- | ----------- |
+| `duration` | The rendering time of the view |
+| `view` | The path of the view, relative to the application's root directory |
+
+The `action` tag contains the action name of the transaction that rendered the
+view.
+
+---
+
+Read more on:
+
+- [Introduction to GitLab Performance Monitoring](introduction.md)
+- [GitLab Configuration](gitlab_configuration.md)
+- [InfluxDB Configuration](influxdb_configuration.md)
diff --git a/doc/monitoring/performance/introduction.md b/doc/monitoring/performance/introduction.md
new file mode 100644
index 0000000000000000000000000000000000000000..f2460d3130203f33379701a01a408f54a246ab44
--- /dev/null
+++ b/doc/monitoring/performance/introduction.md
@@ -0,0 +1,64 @@
+# GitLab Performance Monitoring
+
+GitLab comes with its own application performance measuring system as of GitLab
+8.4, simply called "GitLab Performance Monitoring". GitLab Performance Monitoring is available in both the
+Community and Enterprise editions.
+
+Apart from this introduction, you are advised to read through the following
+documents in order to understand and properly configure GitLab Performance Monitoring:
+
+- [GitLab Configuration](gitlab_configuration.md)
+- [InfluxDB Configuration](influxdb_configuration.md)
+- [InfluxDB Schema](influxdb_schema.md)
+
+## Introduction to GitLab Performance Monitoring
+
+GitLab Performance Monitoring makes it possible to measure a wide variety of statistics
+including (but not limited to):
+
+- The time it took to complete a transaction (a web request or Sidekiq job).
+- The time spent in running SQL queries and rendering HAML views.
+- The time spent executing (instrumented) Ruby methods.
+- Ruby object allocations, and retained objects in particular.
+- System statistics such as the process' memory usage and open file descriptors.
+- Ruby garbage collection statistics.
+
+Metrics data is written to [InfluxDB][influxdb] over [UDP][influxdb-udp]. Stored
+data can be visualized using [Grafana][grafana] or any other application that
+supports reading data from InfluxDB. Alternatively data can be queried using the
+InfluxDB CLI.
+
+## Metric Types
+
+Two types of metrics are collected:
+
+1. Transaction specific metrics.
+1. Sampled metrics, collected at a certain interval in a separate thread.
+
+### Transaction Metrics
+
+Transaction metrics are metrics that can be associated with a single
+transaction. This includes statistics such as the transaction duration, timings
+of any executed SQL queries, time spent rendering HAML views, etc. These metrics
+are collected for every Rack request and Sidekiq job processed.
+
+### Sampled Metrics
+
+Sampled metrics are metrics that can't be associated with a single transaction.
+Examples include garbage collection statistics and retained Ruby objects. These
+metrics are collected at a regular interval. This interval is made up out of two
+parts:
+
+1. A user defined interval.
+1. A randomly generated offset added on top of the interval, the same offset
+   can't be used twice in a row.
+
+The actual interval can be anywhere between a half of the defined interval and a
+half above the interval. For example, for a user defined interval of 15 seconds
+the actual interval can be anywhere between 7.5 and 22.5. The interval is
+re-generated for every sampling run instead of being generated once and re-used
+for the duration of the process' lifetime.
+
+[influxdb]: https://influxdata.com/time-series-platform/influxdb/
+[influxdb-udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/
+[grafana]: http://grafana.org/
diff --git a/doc/profile/preferences.md b/doc/profile/preferences.md
index f17bbe8f2aa36703bd8c7a9a2fce9477a4ff72cc..073b87975081e559e67810205c7e32a8dbb2cafb 100644
--- a/doc/profile/preferences.md
+++ b/doc/profile/preferences.md
@@ -12,6 +12,9 @@ The default is **Charcoal**.
 
 ## Syntax highlighting theme
 
+_GitLab uses the [rouge ruby library][rouge] for syntax highlighting. For a
+list of supported languages visit the rouge website._
+
 Changing this setting allows the user to customize the theme used when viewing
 syntax highlighted code on the site.
 
@@ -36,3 +39,5 @@ The default is **Your Projects**.
 It allows user to choose what content he or she want to see on project page.
 
 The default is **Readme**.
+
+[rouge]: http://rouge.jneen.net/ "Rouge website"
diff --git a/doc/project_services/img/jira_add_gitlab_commit_message.png b/doc/project_services/img/jira_add_gitlab_commit_message.png
new file mode 100644
index 0000000000000000000000000000000000000000..85e54861b3e830a1712c70548835c64ae9b63e19
Binary files /dev/null and b/doc/project_services/img/jira_add_gitlab_commit_message.png differ
diff --git a/doc/project_services/img/jira_add_user_to_group.png b/doc/project_services/img/jira_add_user_to_group.png
new file mode 100644
index 0000000000000000000000000000000000000000..e4576433889c8bbb28810e72481cbed6eed5154e
Binary files /dev/null and b/doc/project_services/img/jira_add_user_to_group.png differ
diff --git a/doc/project_services/img/jira_create_new_group.png b/doc/project_services/img/jira_create_new_group.png
new file mode 100644
index 0000000000000000000000000000000000000000..edaa132605833816be8c85498a482f263f0c0bd5
Binary files /dev/null and b/doc/project_services/img/jira_create_new_group.png differ
diff --git a/doc/project_services/img/jira_create_new_group_name.png b/doc/project_services/img/jira_create_new_group_name.png
new file mode 100644
index 0000000000000000000000000000000000000000..9e518ad7843e658855c2f4f246f0425eaccbec25
Binary files /dev/null and b/doc/project_services/img/jira_create_new_group_name.png differ
diff --git a/doc/project_services/img/jira_create_new_user.png b/doc/project_services/img/jira_create_new_user.png
new file mode 100644
index 0000000000000000000000000000000000000000..57e433dd8184786120e6301bbbea2ed39e029342
Binary files /dev/null and b/doc/project_services/img/jira_create_new_user.png differ
diff --git a/doc/project_services/img/jira_group_access.png b/doc/project_services/img/jira_group_access.png
new file mode 100644
index 0000000000000000000000000000000000000000..47716ca6d0e782e7e17900140a01826ec7825285
Binary files /dev/null and b/doc/project_services/img/jira_group_access.png differ
diff --git a/doc/project_services/img/jira_issue_closed.png b/doc/project_services/img/jira_issue_closed.png
new file mode 100644
index 0000000000000000000000000000000000000000..cabec1ae137cd264dc3fa3924cebb7a6338a711b
Binary files /dev/null and b/doc/project_services/img/jira_issue_closed.png differ
diff --git a/doc/integration/img/jira_issue_reference.png b/doc/project_services/img/jira_issue_reference.png
similarity index 100%
rename from doc/integration/img/jira_issue_reference.png
rename to doc/project_services/img/jira_issue_reference.png
diff --git a/doc/project_services/img/jira_issues_workflow.png b/doc/project_services/img/jira_issues_workflow.png
new file mode 100644
index 0000000000000000000000000000000000000000..28e17be3a84371f41cd8d44b869ad01ea655ee87
Binary files /dev/null and b/doc/project_services/img/jira_issues_workflow.png differ
diff --git a/doc/integration/img/jira_merge_request_close.png b/doc/project_services/img/jira_merge_request_close.png
similarity index 100%
rename from doc/integration/img/jira_merge_request_close.png
rename to doc/project_services/img/jira_merge_request_close.png
diff --git a/doc/integration/img/jira_project_name.png b/doc/project_services/img/jira_project_name.png
similarity index 100%
rename from doc/integration/img/jira_project_name.png
rename to doc/project_services/img/jira_project_name.png
diff --git a/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png b/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png
new file mode 100644
index 0000000000000000000000000000000000000000..0149181dc86a26643bbf49ded08a045580bc2518
Binary files /dev/null and b/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png differ
diff --git a/doc/integration/img/jira_service.png b/doc/project_services/img/jira_service.png
similarity index 100%
rename from doc/integration/img/jira_service.png
rename to doc/project_services/img/jira_service.png
diff --git a/doc/integration/img/jira_service_close_issue.png b/doc/project_services/img/jira_service_close_issue.png
similarity index 100%
rename from doc/integration/img/jira_service_close_issue.png
rename to doc/project_services/img/jira_service_close_issue.png
diff --git a/doc/integration/img/jira_service_page.png b/doc/project_services/img/jira_service_page.png
similarity index 100%
rename from doc/integration/img/jira_service_page.png
rename to doc/project_services/img/jira_service_page.png
diff --git a/doc/project_services/img/jira_submit_gitlab_merge_request.png b/doc/project_services/img/jira_submit_gitlab_merge_request.png
new file mode 100644
index 0000000000000000000000000000000000000000..e935d9362aacd90616ee5fd2ce00fa12d1e56617
Binary files /dev/null and b/doc/project_services/img/jira_submit_gitlab_merge_request.png differ
diff --git a/doc/project_services/img/jira_user_management_link.png b/doc/project_services/img/jira_user_management_link.png
new file mode 100644
index 0000000000000000000000000000000000000000..2745916972c87697e6e006ca5ba2737c433d8b64
Binary files /dev/null and b/doc/project_services/img/jira_user_management_link.png differ
diff --git a/doc/integration/img/jira_workflow_screenshot.png b/doc/project_services/img/jira_workflow_screenshot.png
similarity index 100%
rename from doc/integration/img/jira_workflow_screenshot.png
rename to doc/project_services/img/jira_workflow_screenshot.png
diff --git a/doc/project_services/jira.md b/doc/project_services/jira.md
new file mode 100644
index 0000000000000000000000000000000000000000..d6b2e7f521bb6277160563e9c1277e7e337d811b
--- /dev/null
+++ b/doc/project_services/jira.md
@@ -0,0 +1,212 @@
+# GitLab JIRA integration
+
+GitLab can be configured to interact with [JIRA Core] either using an
+on-premises instance or the SaaS solution that Atlassian offers. Configuration
+happens via username and password on a per-project basis. Connecting to a JIRA
+server via CAS is not possible.
+
+Each project can be configured to connect to a different JIRA instance or, in
+case you have a single JIRA instance, you can pre-fill the JIRA service
+settings page in GitLab with a default template. To configure the JIRA template,
+see the [Services Templates documentation][services-templates].
+
+Once the GitLab project is connected to JIRA, you can reference and close the
+issues in JIRA directly from GitLab's merge requests.
+
+## Configuration
+
+The configuration consists of two parts:
+
+- [JIRA configuration](#configuring-jira)
+- [GitLab configuration](#configuring-gitlab)
+
+### Configuring JIRA
+
+First things first, we need to create a user in JIRA which will have access to
+all projects that need to integrate with GitLab.
+
+We have split this stage in steps so it is easier to follow.
+
+---
+
+1. Login to your JIRA instance as an administrator and under **Administration**
+   go to **User Management** to create a new user.
+
+     ![JIRA user management link](img/jira_user_management_link.png)
+
+     ---
+
+1. The next step is to create a new user (e.g., `gitlab`) who has write access
+   to projects in JIRA. Enter the user's name and a _valid_ e-mail address
+   since JIRA sends a verification e-mail to set-up the password.
+   _**Note:** JIRA creates the username automatically by using the e-mail
+   prefix. You can change it later if you want._
+
+     ![JIRA create new user](img/jira_create_new_user.png)
+
+     ---
+
+1. Now, let's create a `gitlab-developers` group which will have write access
+   to projects in JIRA. Go to the **Groups** tab and select **Create group**.
+
+     ![JIRA create new user](img/jira_create_new_group.png)
+
+     ---
+
+     Give it an optional description and hit **Create group**.
+
+     ![JIRA create new group](img/jira_create_new_group_name.png)
+
+     ---
+
+1. Give the newly-created group write access by going to
+   **Application access > View configuration** and adding the `gitlab-developers`
+   group to JIRA Core.
+
+     ![JIRA group access](img/jira_group_access.png)
+
+     ---
+
+1. Add the `gitlab` user to the `gitlab-developers` group by going to
+   **Users > GitLab user > Add group** and selecting the `gitlab-developers`
+   group from the dropdown menu. Notice that the group says _Access_ which is
+   what we aim for.
+
+     ![JIRA add user to group](img/jira_add_user_to_group.png)
+
+---
+
+The JIRA configuration is over. Write down the new JIRA username and its
+password as they will be needed when configuring GitLab in the next section.
+
+### Configuring GitLab
+
+_**Note:** The currently supported JIRA versions are v6.x and v7.x. and GitLab
+7.8 or higher is required._
+
+---
+
+Assuming you [have already configured JIRA](#configuring-jira), now it's time
+to configure GitLab.
+
+JIRA configuration in GitLab is done via a project's
+[**Services**](../project_services/project_services.md).
+
+To enable JIRA integration in a project, navigate to the project's
+**Settings > Services > JIRA**.
+
+Fill in the required details on the page, as described in the table below.
+
+| Setting | Description |
+| ------- | ----------- |
+| `Description` | A name for the issue tracker (to differentiate between instances, for example). |
+| `Project url` | The URL to the JIRA project which is being linked to this GitLab project. It is of the form: `https://<jira_host_url>/issues/?jql=project=<jira_project>`. |
+| `Issues url`  | The URL to the JIRA project issues overview for the project that is linked to this GitLab project. It is of the form: `https://<jira_host_url>/browse/:id`. Leave `:id` as-is, it gets replaced by GitLab at runtime. |
+| `New issue url` | This is the URL to create a new issue in JIRA for the project linked to this GitLab project, and it is of the form: `https://<jira_host_url>/secure/CreateIssue.jspa` |
+| `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`. It is of the form: `https://<jira_host_url>/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 setting is very important to set up correctly. It is the ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot](img/jira_issues_workflow.png)). By default, this ID is set to `2` |
+
+After saving the configuration, your GitLab project will be able to interact
+with the linked JIRA project.
+
+![JIRA service page](img/jira_service_page.png)
+
+---
+
+## JIRA issues
+
+By now you should have [configured JIRA](#configuring-jira) and enabled the
+[JIRA service in GitLab](#configuring-gitlab). If everything is set up correctly
+you should be able to reference and close JIRA issues by just mentioning their
+ID in GitLab commits and merge requests.
+
+### Referencing JIRA Issues
+
+If you reference a JIRA issue, e.g., `GITLAB-1`, in a commit comment, a link
+which points back to JIRA is created.
+
+The same works for comments in merge requests as well.
+
+![JIRA add GitLab commit message](img/jira_add_gitlab_commit_message.png)
+
+---
+
+The mentioning action is two-fold, so a comment with a JIRA issue in GitLab
+will automatically add a comment in that particular JIRA issue with the link
+back to GitLab.
+
+
+![JIRA reference commit message](img/jira_reference_commit_message_in_jira_issue.png)
+
+---
+
+The comment on the JIRA issue is of the form:
+
+> USER mentioned this issue in LINK_TO_THE_MENTION
+
+Where:
+
+| Format | Description |
+| ------ | ----------- |
+| `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. |
+
+### Closing JIRA issues
+
+JIRA issues can be closed directly from GitLab by using trigger words in
+commits and merge requests. When a commit which contains the trigger word
+followed by the JIRA issue ID in the commit message is pushed, GitLab will
+add a comment in the mentioned JIRA issue and immediately close it (provided
+the transition ID was set up correctly).
+
+There are currently three trigger words, and you can use either one to achieve
+the same goal:
+
+- `Resolves GITLAB-1`
+- `Closes GITLAB-1`
+- `Fixes GITLAB-1`
+
+where `GITLAB-1` the issue ID of the JIRA project.
+
+### JIRA issue closing example
+
+Let's say for example that we submitted a bug fix and created a merge request
+in GitLab. The workflow would be something like this:
+
+1. Create a new branch
+1. Fix the bug
+1. Commit the changes and push branch to GitLab
+1. Open a new merge request and reference the JIRA issue including one of the
+   trigger words, e.g.: `Fixes GITLAB-1`, in the description
+1. Submit the merge request
+1. Ask someone to review
+1. Merge the merge request
+1. The JIRA issue is automatically closed
+
+---
+
+In the following screenshot you can see what the link references to the JIRA
+issue look like.
+
+![JIRA - submit a GitLab merge request](img/jira_submit_gitlab_merge_request.png)
+
+---
+
+Once this merge request is merged, the JIRA issue will be automatically closed
+with a link to the commit that resolved the issue.
+
+![The GitLab integration user leaves a comment on JIRA](img/jira_issue_closed.png)
+
+---
+
+You can see from the above image that there are four references to GitLab:
+
+- The first is from a comment in a specific commit
+- The second is from the JIRA issue reference in the merge request description
+- The third is from the actual commit that solved the issue
+- And the fourth is from the commit that the merge request created
+
+[services-templates]: ../project_services/services_templates.md "Services templates documentation"
+[JIRA Core]: https://www.atlassian.com/software/jira/core "The JIRA Core website"
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index e34031277230d60e3232dcbf370536b78a1c091e..55db3e4f2f3d842a58da6e8799bcb43fe6cd689b 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -22,7 +22,7 @@ further configuration instructions and details. Contributions are welcome.
 | Gemnasium | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities |
 | [HipChat](hipchat.md) | Private group chat and IM |
 | [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway |
-| JIRA | Jira issue tracker |
+| [JIRA](jira.md) | JIRA issue tracker |
 | JetBrains TeamCity CI | A continuous integration and build server |
 | PivotalTracker | Project Management Software (Source Commits Endpoint) |
 | Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
diff --git a/doc/security/README.md b/doc/security/README.md
index f34c792d00549596d2860c18f2985d84bca37090..be1abb88c3db6d19a12efb0378107bcb614bdb7e 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -7,4 +7,4 @@
 - [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)
+- [Enforce Two-factor authentication](two_factor_authentication.md)
diff --git a/doc/security/img/two_factor_authentication_settings.png b/doc/security/img/two_factor_authentication_settings.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa51ce030bb77346686f49633b67c3a2b262481e
Binary files /dev/null and b/doc/security/img/two_factor_authentication_settings.png differ
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
index 4e25a1fdc3ff381c271726b968da2e01d0b97f79..8365bdb7b1b38b504cf1cf9db1b6ad154868623d 100644
--- a/doc/security/two_factor_authentication.md
+++ b/doc/security/two_factor_authentication.md
@@ -20,7 +20,13 @@ 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`
+period to `0`.
+
+---
+
+![Two factor authentication admin settings](img/two_factor_authentication_settings.png)
+
+---
 
 ## Disabling 2FA for everyone
 
@@ -28,11 +34,12 @@ 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
+# Omnibus installations
 sudo gitlab-rake gitlab:two_factor:disable_for_all_users
 
-# if you've installed GitLab from source
+# Installations 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.**
+**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/update/8.3-to-8.4.md b/doc/update/8.3-to-8.4.md
index 604939cd73380bfeeff314bdadb5f0a6d26ccdf8..616b1f58b65618410b9e4a134e6f13f39414e224 100644
--- a/doc/update/8.3-to-8.4.md
+++ b/doc/update/8.3-to-8.4.md
@@ -37,7 +37,7 @@ sudo -u git -H git checkout 8-4-stable-ee
 ```bash
 cd /home/git/gitlab-shell
 sudo -u git -H git fetch --all
-sudo -u git -H git checkout v2.6.9
+sudo -u git -H git checkout v2.6.10
 ```
 
 ### 5. Update gitlab-workhorse
@@ -48,7 +48,7 @@ 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.6.0
+sudo -u git -H git checkout 0.6.2
 sudo -u git -H make
 ```
 
@@ -104,10 +104,7 @@ via [/etc/default/gitlab].
 
 #### 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.
+We updated the init script for GitLab in order to set a specific PATH for gitlab-workhorse.
 
 ```
 cd /home/git/gitlab
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 6420d65cf1bcb0eed818a490f6dacb892bd0c290..c29037e89c2761d092d287ada510918ff9bb14cb 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -56,7 +56,7 @@ X-Gitlab-Event: Push Hook
       "author": {
         "name": "Jordi Mallach",
         "email": "jordi@softcatala.org"
-      }
+      },
       "added": ["CHANGELOG"],
       "modified": ["app/controller/application.rb"],
       "removed": []
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 3651b55f438ee3c9d45e56e6daf69dd5e8c6a3b3..bf62ab4105329e8a671ef681ac2b78c566d8cdbf 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -6,6 +6,7 @@
 - [GitLab Flow](gitlab_flow.md)
 - [Groups](groups.md)
 - [Keyboard shortcuts](shortcuts.md)
+- [File finder](file_finder.md)
 - [Labels](labels.md)
 - [Notification emails](notifications.md)
 - [Project Features](project_features.md)
diff --git a/doc/workflow/file_finder.md b/doc/workflow/file_finder.md
new file mode 100644
index 0000000000000000000000000000000000000000..b69ae663272859d5c35d03ae8ea41b20f40179db
--- /dev/null
+++ b/doc/workflow/file_finder.md
@@ -0,0 +1,46 @@
+# File finder
+
+_**Note:** This feature was [introduced][gh-9889] in GitLab 8.4._
+
+---
+
+The file finder feature allows you to quickly shortcut your way when you are
+searching for a file in a repository using the GitLab UI.
+
+You can find the **Find File** button when in the **Files** section of a
+project.
+
+![Find file button](img/file_finder_find_button.png)
+
+---
+
+For those who prefer to keep their fingers on the keyboard, there is a
+[shortcut button](shortcuts.md) as well, which you can invoke from _anywhere_
+in a project.
+
+Press `t` to launch the File search function when in **Issues**,
+**Merge requests**, **Milestones**, even the project's settings.
+
+Start typing what you are searching for and watch the magic happen. With the
+up/down arrows, you go up and down the results, with `Esc` you close the search
+and go back to **Files**.
+
+## How it works
+
+The File finder feature is powered by the [Fuzzy filter] library.
+
+It implements a fuzzy search with highlight, and tries to provide intuitive
+results by recognizing patterns that people use while searching.
+
+For example, consider the [GitLab CE repository][ce] and that we want to open
+the `app/controllers/admin/deploy_keys_controller.rb` file.
+
+Using fuzzy search, we start by typing letters that get us closer to the file.
+
+**Protip:** To narrow down your search, include `/` in your search terms.
+
+![Find file button](img/file_finder_find_file.png)
+
+[gh-9889]: https://github.com/gitlabhq/gitlabhq/pull/9889 "File finder pull request"
+[fuzzy filter]: https://github.com/jeancroy/fuzzaldrin-plus "fuzzaldrin-plus on GitHub"
+[ce]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master "GitLab CE repository"
diff --git a/doc/workflow/forking/fork_button.png b/doc/workflow/forking/fork_button.png
deleted file mode 100644
index def4266476ad23531e7299ace79fa4e999b3d94c..0000000000000000000000000000000000000000
Binary files a/doc/workflow/forking/fork_button.png and /dev/null differ
diff --git a/doc/workflow/forking/groups.png b/doc/workflow/forking/groups.png
deleted file mode 100644
index 3ac64b3c8e746317376a7e0c6fa2b1df9e7a4569..0000000000000000000000000000000000000000
Binary files a/doc/workflow/forking/groups.png and /dev/null differ
diff --git a/doc/workflow/forking_workflow.md b/doc/workflow/forking_workflow.md
index 8edf7c6ab3d203ea3389f18a28bf8d6b30974e82..217a4a4012f44257e03eae26add800a5d0926e4e 100644
--- a/doc/workflow/forking_workflow.md
+++ b/doc/workflow/forking_workflow.md
@@ -1,36 +1,59 @@
 # Project forking workflow
 
-Forking a project to your own namespace is useful if you have no write access to the project you want to contribute
-to. If you do have write access or can request it we recommend working together in the same repository since it is simpler.
-See our **[GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)** article for more information about using
-branches to work together.
+Forking a project to your own namespace is useful if you have no write
+access to the project you want to contribute to. If you do have write
+access or can request it, we recommend working together in the same
+repository since it is simpler. See our [GitLab Flow](gitlab_flow.md)
+document more information about using branches to work together.
 
 ## Creating a fork
 
-In order to create a fork of a project, all you need to do is click on the fork button located on the top right side
-of the screen, close to the project's URL and right next to the stars button.
+Forking a project is in most cases a two-step process.
 
-![Fork button](forking/fork_button.png)
 
-Once you do that you'll be presented with a screen where you can choose the namespace to fork to. Only namespaces
-(groups and your own namespace) where you have write access to, will be shown. Click on the namespace to create your
-fork there.
+1.  Click on the fork button located in the middle of the page or a project's
+    home page right next to the stars button.
 
-![Groups view](forking/groups.png)
+    ![Fork button](img/forking_workflow_fork_button.png)
 
-After the forking is done, you can start working on the newly created repository. There you will have full
-[Owner](../permissions/permissions.md) access, so you can set it up as you please.
+    ---
+
+1.  Once you do that, you'll be presented with a screen where you can choose
+    the namespace to fork to. Only namespaces (groups and your own
+    namespace) where you have write access to, will be shown. Click on the
+    namespace to create your fork there.
+
+    ![Choose namespace](img/forking_workflow_choose_namespace.png)
+
+    ---
+
+    **Note:**
+    If the namespace you chose to fork the project to has another project with
+    the same path name, you will be presented with a warning that the forking
+    could not be completed. Try to resolve the error and repeat the forking
+    process.
+
+    ![Path taken error](img/forking_workflow_path_taken_error.png)
+
+    ---
+
+After the forking is done, you can start working on the newly created
+repository. There, you will have full [Owner](../permissions/permissions.md)
+access, so you can set it up as you please.
 
 ## Merging upstream
 
-Once you are ready to send your code back to the main project, you need to create a merge request. Choose your forked
-project's main branch as the source and the original project's main branch as the destination and create the merge request.
+Once you are ready to send your code back to the main project, you need
+to create a merge request. Choose your forked project's main branch as
+the source and the original project's main branch as the destination and
+create the [merge request](merge_requests.md).
 
 ![Selecting branches](forking/branch_select.png)
 
-You can then assign the merge request to someone to have them review your changes. Upon pressing the 'Accept Merge Request'
-button, your changes will be added to the repository and branch you're merging into.
+You can then assign the merge request to someone to have them review
+your changes. Upon pressing the 'Accept Merge Request' button, your
+changes will be added to the repository and branch you're merging into.
 
 ![New merge request](forking/merge_request.png)
 
-
+[gitlab flow]: https://about.gitlab.com/2014/09/29/gitlab-flow/ "GitLab Flow blog post"
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 8965e5b365442da9e3a0a4d3f30760eedecd5c10..be32f0c720b280b75ee74dec021d945729c4f1f2 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -152,9 +152,10 @@ The name of this branch should start with the issue number, for example '15-requ
 
 When you are done or want to discuss the code you open a merge request.
 This is an online place to discuss the change and review the code.
-Creating a branch is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
-If you create the merge request but do not assign it to anyone it is a 'work-in-process' merge request.
+Opening a merge request is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
+If you open the merge request but do not assign it to anyone it is a 'Work In Progress' merge request.
 These are used to discuss the proposed implementation but are not ready for inclusion in the master branch yet.
+_Pro tip:_ Start the title of the merge request with `[WIP]` or `WIP:` to prevent it from being merged before it's ready.
 
 When the author thinks the code is ready the merge request is assigned to reviewer.
 The reviewer presses the merge button when they think the code is ready for inclusion in the master branch.
@@ -185,7 +186,7 @@ If you have an issue that spans across multiple repositories, the best thing is
 
 ![Vim screen showing the rebase view](rebase.png)
 
-With git you can use an interactive rebase (rebase -i) to squash multiple commits into one and reorder them.
+With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
 This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
 However you should never rebase commits you have pushed to a remote server.
 Somebody can have referred to the commits or cherry-picked them.
diff --git a/doc/workflow/img/file_finder_find_button.png b/doc/workflow/img/file_finder_find_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5005d0d7cab1799875f75a55f74774560eb9a65
Binary files /dev/null and b/doc/workflow/img/file_finder_find_button.png differ
diff --git a/doc/workflow/img/file_finder_find_file.png b/doc/workflow/img/file_finder_find_file.png
new file mode 100644
index 0000000000000000000000000000000000000000..58500f4c163fbbe6e7889eb69a975aec95bfe2f3
Binary files /dev/null and b/doc/workflow/img/file_finder_find_file.png differ
diff --git a/doc/workflow/img/forking_workflow_choose_namespace.png b/doc/workflow/img/forking_workflow_choose_namespace.png
new file mode 100644
index 0000000000000000000000000000000000000000..eefe57695547cf1719b2f748710fe0937ad8d38a
Binary files /dev/null and b/doc/workflow/img/forking_workflow_choose_namespace.png differ
diff --git a/doc/workflow/img/forking_workflow_fork_button.png b/doc/workflow/img/forking_workflow_fork_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..49e68d33e89084f04c9c96ff8f85c08beba5ec30
Binary files /dev/null and b/doc/workflow/img/forking_workflow_fork_button.png differ
diff --git a/doc/workflow/img/forking_workflow_path_taken_error.png b/doc/workflow/img/forking_workflow_path_taken_error.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a3139506fe02418ca51d2e829f3cfa11867f5c7
Binary files /dev/null and b/doc/workflow/img/forking_workflow_path_taken_error.png differ
diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature
index b667b587c5bcff42602ff188930590a14c331c48..c3b3577c449c42a727bd5b83327896046522ce08 100644
--- a/features/dashboard/dashboard.feature
+++ b/features/dashboard/dashboard.feature
@@ -41,3 +41,33 @@ Feature: Dashboard
     And user with name "John Doe" left project "Shop"
     When I visit dashboard activity page
     Then I should see "John Doe left project Shop" event
+
+  @javascript
+  Scenario: Sorting Issues
+    Given I visit dashboard issues page
+    And I sort the list by "Oldest updated"
+    And I visit dashboard activity page
+    And I visit dashboard issues page
+    Then The list should be sorted by "Oldest updated"
+
+  @javascript
+  Scenario: Visiting Project's issues after sorting
+    Given I visit dashboard issues page
+    And I sort the list by "Oldest updated"
+    And I visit project "Shop" issues page
+    Then The list should be sorted by "Oldest updated"
+
+  @javascript
+  Scenario: Sorting Merge Requests
+    Given I visit dashboard merge requests page
+    And I sort the list by "Oldest updated"
+    And I visit dashboard activity page
+    And I visit dashboard merge requests page
+    Then The list should be sorted by "Oldest updated"
+
+  @javascript
+  Scenario: Visiting Project's merge requests after sorting
+    Given I visit dashboard merge requests page
+    And I sort the list by "Oldest updated"
+    And I visit project "Shop" merge requests page
+    Then The list should be sorted by "Oldest updated"
diff --git a/features/groups.feature b/features/groups.feature
index c803e9529803b493abdd2db4d983b5baf11864d5..55fffb012ae2c413080f30cb29f0e43165f31a84 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -3,6 +3,10 @@ Feature: Groups
     Given I sign in as "John Doe"
     And "John Doe" is owner of group "Owned"
 
+  Scenario: I should not see a group if it does not exist
+    When I visit group "NonExistentGroup" page
+    Then page status code should be 404
+
   Scenario: I should have back to group button
     When I visit group "Owned" page
     Then I should see back to dashboard button
diff --git a/features/project/builds/artifacts.feature b/features/project/builds/artifacts.feature
index 1185854453a00b677af178d9a1cd85436ca461ad..52dc15f2eb665639561479c295f5dc61d5099f1a 100644
--- a/features/project/builds/artifacts.feature
+++ b/features/project/builds/artifacts.feature
@@ -51,3 +51,12 @@ Feature: Project Builds Artifacts
     And I click artifacts browse button
     And I click a link to file within build artifacts
     Then download of a file extracted from build artifacts should start
+
+  @javascript
+  Scenario: I click on a row in an artifacts table
+    Given recent build has artifacts available
+    And recent build has artifacts metadata available
+    When I visit recent build details page
+    And I click artifacts browse button
+    And I click a first row within build artifacts table
+    Then page with a coresponding path is loading
diff --git a/features/project/fork.feature b/features/project/fork.feature
index 37cd53ee9775ddeab8a40a261a06d0c56d80e9bf..12695204e4718a2458022592386e792f53a2e878 100644
--- a/features/project/fork.feature
+++ b/features/project/fork.feature
@@ -25,3 +25,18 @@ Feature: Project Fork
     Then I should see "New merge request"
     And I click link "New merge request"
     Then I should see the new merge request page for my namespace
+
+  Scenario: Viewing forks of a Project
+    Given I click link "Fork"
+    When I fork to my namespace
+    And I visit the forks page of the "Shop" project
+    Then I should see my fork on the list
+
+  Scenario: Viewing private forks of a Project
+    Given There is an existent fork of the "Shop" project
+    And I click link "Fork"
+    When I fork to my namespace
+    And I visit the forks page of the "Shop" project
+    Then I should see my fork on the list
+    And I should not see the other fork listed
+    And I should see a private fork notice
diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature
index 9a06fdc2ee6eb5ea0a40a84ea441a31cc3b5ebdc..bfde89fd8969ab89842c1a435f06e1828eb10dd2 100644
--- a/features/project/issues/award_emoji.feature
+++ b/features/project/issues/award_emoji.feature
@@ -9,6 +9,7 @@ Feature: Award Emoji
   @javascript
   Scenario: I add and remove award in the issue
     Given I click to emoji-picker
+    Then The search field is focused
     And I click to emoji in the picker
     Then I have award added
     And I can remove it by clicking to icon
@@ -16,11 +17,13 @@ Feature: Award Emoji
   @javascript
   Scenario: I can see the list of emoji categories
     Given I click to emoji-picker
+    Then The search field is focused
     Then I can see the activity and food categories
 
   @javascript
   Scenario: I can search emoji
     Given I click to emoji-picker
+    Then The search field is focused
     And I search "hand"
     Then I see search result for "hand"
 
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index ab234bc7507dc3766aaa944e2d238c051f35ca89..0b3d03aa2a58fdcbbd2c7f9b9bff63ab3a3b319b 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -51,6 +51,36 @@ Feature: Project Issues
     Then I should see comment "XML attached"
     And I should see an error alert section within the comment form
 
+  @javascript
+  Scenario: Visiting Issues after leaving a comment
+    Given I visit issue page "Release 0.4"
+    And I leave a comment like "XML attached"
+    And I visit project "Shop" issues page
+    And I sort the list by "Last updated"
+    Then I should see "Release 0.4" at the top
+
+  @javascript
+  Scenario: Visiting Issues after being sorted the list
+    Given I visit project "Shop" issues page
+    And I sort the list by "Oldest updated"
+    And I visit my project's home page
+    And I visit project "Shop" issues page
+    Then The list should be sorted by "Oldest updated"
+
+  @javascript
+  Scenario: Visiting Merge Requests after being sorted the list
+    Given I visit project "Shop" issues page
+    And I sort the list by "Oldest updated"
+    And I visit project "Shop" merge requests page
+    Then The list should be sorted by "Oldest updated"
+
+  @javascript
+  Scenario: Visiting Merge Requests from a differente Project after sorting
+    Given I visit project "Shop" merge requests page
+    And I sort the list by "Oldest updated"
+    And I visit dashboard merge requests page
+    Then The list should be sorted by "Oldest updated"
+
   @javascript
   Scenario: I search issue
     Given I fill in issue search with "Re"
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index aa9078b878f9d3c39d1b2650d071a501b684e1d8..ca1ee6b3c2b8935475870b82de4e4887f330173c 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -75,6 +75,47 @@ Feature: Project Merge Requests
     And I leave a comment like "XML attached"
     Then I should see comment "XML attached"
 
+  @javascript
+  Scenario: Visiting Merge Requests after leaving a comment
+    Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+    And I visit merge request page "Bug NS-04"
+    And I leave a comment like "XML attached"
+    And I visit project "Shop" merge requests page
+    And I sort the list by "Last updated"
+    Then I should see "Bug NS-04" at the top
+
+  @javascript
+  Scenario: Visiting Merge Requests after being sorted the list
+    Given I visit project "Shop" merge requests page
+    And I sort the list by "Oldest updated"
+    And I visit my project's home page
+    And I visit project "Shop" merge requests page
+    Then The list should be sorted by "Oldest updated"
+
+  @javascript
+  Scenario: Visiting Issues after being sorted the list
+    Given I visit project "Shop" merge requests page
+    And I sort the list by "Oldest updated"
+    And I visit project "Shop" issues page
+    Then The list should be sorted by "Oldest updated"
+
+  @javascript
+  Scenario: Visiting Merge Requests from a differente Project after sorting
+    Given I visit project "Shop" merge requests page
+    And I sort the list by "Oldest updated"
+    And I visit dashboard merge requests page
+    Then The list should be sorted by "Oldest updated"
+
+  @javascript
+  Scenario: Visiting Merge Requests after commenting on diffs
+    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 visit project "Shop" merge requests page
+    And I sort the list by "Last updated"
+    Then I should see "Bug NS-05" at the top
+
   @javascript
   Scenario: I comment on a merge request diff
     Given project "Shop" have "Bug NS-05" open merge request with diffs inside
@@ -83,6 +124,15 @@ Feature: Project Merge Requests
     And I leave a comment like "Line is wrong" on diff
     And I switch to the merge request's comments tab
     Then I should see a discussion has started on diff
+    And I should see a badge of "1" next to the discussion link
+
+  @javascript
+  Scenario: I see a new comment on merge request diff from another user in the discussion tab
+    Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+    And I visit merge request page "Bug NS-05"
+    And user "John Doe" leaves a comment like "Line is wrong" on diff
+    Then I should see a discussion by user "John Doe" has started on diff
+    And I should see a badge of "1" next to the discussion link
 
   @javascript
   Scenario: I edit a comment on a merge request diff
@@ -100,9 +150,11 @@ Feature: Project Merge Requests
     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 should see a badge of "1" next to the discussion link
     And I delete the comment "Line is wrong" on diff
     And I click on the Discussion tab
     Then I should not see any discussion
+    And I should see a badge of "0" next to the discussion link
 
   @javascript
   Scenario: I comment on a line of a commit in merge request
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 63f0ec2b6e86dea2712621482591ecd03110e6f9..5062e3488441ee55a4b452d20252b8f336c81a5e 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -2,6 +2,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
   include SharedAuthentication
   include SharedPaths
   include SharedProject
+  include SharedIssuable
 
   step 'I should see "New Project" link' do
     expect(page).to have_link "New project"
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 4c5122d1b7d505b9d0afe2dae55972cf3e63a541..1e2a78a6029fc718eff3fa1f8ac32eeed889b83e 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -120,6 +120,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
     expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
   end
 
+  step 'I visit group "NonExistentGroup" page' do
+    visit group_path(-1)
+  end
+
   private
 
   def assigned_to_me(key)
diff --git a/features/steps/project/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb
index 25f2f4e837c7588025471e4a5959279ac02fa36f..1bdb57af9d13c47fd74b8161081e9b04e300355c 100644
--- a/features/steps/project/builds/artifacts.rb
+++ b/features/steps/project/builds/artifacts.rb
@@ -73,4 +73,14 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
     expect(response_json[:archive]).to end_with('build_artifacts.zip')
     expect(response_json[:entry]).to eq Base64.encode64('ci_artifacts.txt')
   end
+
+  step 'I click a first row within build artifacts table' do
+    row = first('tr[data-link]')
+    @row_path = row['data-link']
+    row.click
+  end
+
+  step 'page with a coresponding path is loading' do
+    expect(current_path).to eq @row_path
+  end
 end
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index e98bd51ca89265077b3a11b3f5cbfbfb525a79b4..5810276ced3eb88b31334ecdab5ef7e550b9ff61 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -49,4 +49,29 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
   step 'I should see the new merge request page for my namespace' do
     current_path.should have_content(/#{current_user.namespace.name}/i)
   end
+
+  step 'I visit the forks page of the "Shop" project' do
+    @project = Project.where(name: 'Shop').last
+    visit namespace_project_forks_path(@project.namespace, @project)
+  end
+
+  step 'I should see my fork on the list' do
+    page.within('.projects-list-holder') do
+      project = @user.fork_of(@project)
+      expect(page).to have_content("#{project.namespace.human_name} / #{project.name}")
+    end
+  end
+
+  step 'There is an existent fork of the "Shop" project' do
+    user = create(:user, name: 'Mike')
+    @forked_project = Projects::ForkService.new(@project, user).execute
+  end
+
+  step 'I should not see the other fork listed' do
+    expect(page).not_to have_content("#{@forked_project.namespace.human_name} / #{@forked_project.name}")
+  end
+
+  step 'I should see a private fork notice' do
+    expect(page).to have_content("1 private fork")
+  end
 end
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index cbdce78dc0ca300923c36096b632463d35821e09..7e4425ff6625a988449464b49b1cba4f35dc0506 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -43,7 +43,9 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
 
     expect(page).to have_css("h3.page-title", text: "New Merge Request")
 
-    fill_in "merge_request_title", with: "Merge Request On Forked Project"
+    page.within 'form#new_merge_request' do
+      fill_in "merge_request_title", with: "Merge Request On Forked Project"
+    end
   end
 
   step 'I submit the merge request' do
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 2c2ed08655ee722e077139a9e27eeeaca5d9660d..69695d493f39dbcaccca9d681b52ee3efafee3ec 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -66,4 +66,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
       expect(page).to have_selector '[data-emoji="raised_hand"]'
     end
   end
+
+  step 'The search field is focused' do
+    page.evaluate_script("document.activeElement.id").should eq "emoji_search"
+  end
 end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 8e8c9c57452f5218de00570885a90de15efaa855..d556b73f9fdeacb4711281836754f333268484b2 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -293,6 +293,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
       expect(page).to have_content('Yay!')
     end
   end
+
+  step 'I should see "Release 0.4" at the top' do
+    expect(page.find('ul.content-list.issues-list li.issue:first-child')).to have_content("Release 0.4")
+  end
+
   def filter_issue(text)
     fill_in 'issue_search', with: text
   end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index be993d11093edf42d08122f946436980361575b4..337893e6209fa4bf7f55fd29a3cef13c4967a0b8 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -41,7 +41,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
   end
 
   step 'I should not see "master" branch' do
-    expect(page).not_to have_content "master"
+    expect(find('.merge-request-info')).not_to have_content "master"
   end
 
   step 'I should see "other_branch" branch' do
@@ -181,6 +181,15 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
     leave_comment "Line is wrong"
   end
 
+  step 'user "John Doe" leaves a comment like "Line is wrong" on diff' do
+    mr = MergeRequest.find_by(title: "Bug NS-05")
+    create(:note_on_merge_request_diff, project: project,
+                                        noteable_id: mr.id,
+                                        author: user_exists("John Doe"),
+                                        line_code: sample_commit.line_code,
+                                        note: 'Line is wrong')
+  end
+
   step 'I leave a comment like "Line is wrong" on diff in commit' do
     click_diff_line(sample_commit.line_code)
     leave_comment "Line is wrong"
@@ -238,6 +247,22 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
     end
   end
 
+  step 'I should see a discussion by user "John Doe" has started on diff' do
+    page.within(".notes .discussion") do
+      page.should have_content "#{user_exists("John Doe").name} started a discussion"
+      page.should have_content sample_commit.line_code_path
+      page.should have_content "Line is wrong"
+    end
+  end
+
+  step 'I should see a badge of "1" next to the discussion link' do
+    expect_discussion_badge_to_have_counter("1")
+  end
+
+  step 'I should see a badge of "0" next to the discussion link' do
+    expect_discussion_badge_to_have_counter("0")
+  end
+
   step 'I should see a discussion has started on commit diff' do
     page.within(".notes .discussion") do
       page.should have_content "#{current_user.name} started a discussion on commit"
@@ -415,6 +440,14 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
     end
   end
 
+  step 'I should see "Bug NS-05" at the top' do
+    expect(page.find('ul.content-list.mr-list li.merge-request:first-child')).to have_content("Bug NS-05")
+  end
+
+  step 'I should see "Bug NS-04" at the top' do
+    expect(page.find('ul.content-list.mr-list li.merge-request:first-child')).to have_content("Bug NS-04")
+  end
+
   def merge_request
     @merge_request ||= MergeRequest.find_by!(title: "Bug NS-05")
   end
@@ -444,4 +477,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
   def have_visible_content (text)
     have_css("*", text: text, visible: true)
   end
+
+  def expect_discussion_badge_to_have_counter(value)
+    page.within(".notes-tab .badge") do
+      page.should have_content value
+    end
+  end
 end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index d753ae145900a86c35d57b50c606a34061cfab36..2a735afbe7b4911a72720f1bcbb56aaa8eb138fb 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -163,7 +163,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   end
 
   step 'I search for Wiki content' do
-    fill_in "Search in this project", with: "wiki_content"
+    fill_in "Search", with: "wiki_content"
     click_button "Search"
   end
 
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index c6a0ae2ba38e06110bce21311a248aaccd74d27c..06e69441894af6513fb00f07359a446ef8705ce7 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -23,7 +23,7 @@ module SharedDiffNote
     page.within(diff_file_selector) do
       click_diff_line(sample_commit.line_code)
 
-      page.within("form[rel$='#{sample_commit.line_code}']") do
+      page.within("form[id$='#{sample_commit.line_code}']") do
         fill_in "note[note]", with: "Typo, please fix"
         find(".js-comment-button").trigger("click")
         sleep 0.05
@@ -33,7 +33,7 @@ module SharedDiffNote
 
   step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do
     click_parallel_diff_line(sample_commit.line_code, 'old')
-    page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
+    page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do
       fill_in "note[note]", with: "Old comment"
       find(".js-comment-button").trigger("click")
     end
@@ -41,7 +41,7 @@ module SharedDiffNote
 
   step 'I leave a diff comment in a parallel view on the right side like "New comment"' do
     click_parallel_diff_line(sample_commit.line_code, 'new')
-    page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
+    page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do
       fill_in "note[note]", with: "New comment"
       find(".js-comment-button").trigger("click")
     end
@@ -51,7 +51,7 @@ module SharedDiffNote
     page.within(diff_file_selector) do
       click_diff_line(sample_commit.line_code)
 
-      page.within("form[rel$='#{sample_commit.line_code}']") do
+      page.within("form[id$='#{sample_commit.line_code}']") do
         fill_in "note[note]", with: "Should fix it :smile:"
         find('.js-md-preview-button').click
       end
@@ -62,7 +62,7 @@ module SharedDiffNote
     page.within(diff_file_selector) do
       click_diff_line(sample_commit.del_line_code)
 
-      page.within("form[rel$='#{sample_commit.del_line_code}']") do
+      page.within("form[id$='#{sample_commit.del_line_code}']") do
         fill_in "note[note]", with: "DRY this up"
         find('.js-md-preview-button').click
       end
@@ -91,7 +91,7 @@ module SharedDiffNote
     page.within(diff_file_selector) do
       click_diff_line(sample_commit.line_code)
 
-      page.within("form[rel$='#{sample_commit.line_code}']") do
+      page.within("form[id$='#{sample_commit.line_code}']") do
         fill_in 'note[note]', with: ':smile:'
         click_button('Add Comment')
       end
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 4c5f7488efbd8383572c9bd1c277a33d32a7cc18..25c2b476f43b0ed21488b2435ea14a4832710edd 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -106,6 +106,19 @@ module SharedIssuable
     edit_issuable
   end
 
+  step 'I sort the list by "Oldest updated"' do
+    find('button.dropdown-toggle.btn').click
+    page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
+      click_link "Oldest updated"
+    end
+  end
+
+  step 'The list should be sorted by "Oldest updated"' do
+    page.within('div.dropdown.inline.prepend-left-10') do
+      expect(page.find('button.dropdown-toggle.btn')).to have_content('Oldest updated')
+    end
+  end
+
   def create_issuable_for_project(project_name:, title:, type: :issue)
     project = Project.find_by(name: project_name)
 
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index 444d6726f99af7cdd1153c88b08415404c103213..eb6df61b8e6b43d6b905ca7d95608fdf12c70119 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -144,4 +144,11 @@ module SharedNote
       expect(page).to have_content("+1 Awesome!")
     end
   end
+
+  step 'I sort the list by "Last updated"' do
+    find('button.dropdown-toggle.btn').click
+    page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
+      click_link "Last updated"
+    end
+  end
 end
diff --git a/features/support/capybara.rb b/features/support/capybara.rb
index 4156c7ec484611624902b615884bb19e962312be..38069ff8835c541e315b938b30c1a310a2dea450 100644
--- a/features/support/capybara.rb
+++ b/features/support/capybara.rb
@@ -9,10 +9,6 @@ Capybara.register_driver :poltergeist do |app|
   Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout)
 end
 
-Spinach.hooks.on_tag("javascript") do
-  Capybara.current_driver = Capybara.javascript_driver
-end
-
 Capybara.default_wait_time = timeout
 Capybara.ignore_hidden_elements = false
 
@@ -22,3 +18,7 @@ unless ENV['CI'] || ENV['CI_SERVER']
   # Keep only the screenshots generated from the last failing test suite
   Capybara::Screenshot.prune_strategy = :keep_last_run
 end
+
+Spinach.hooks.before_run do
+  TestEnv.warm_asset_cache
+end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 3f528b9f7c09c2de47ec25c6891ed6794ee7c57c..9dacf7c1e861f170f6aa42e40a6bb4431d33ddac 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -153,10 +153,11 @@ module API
     end
 
     def attributes_for_keys(keys, custom_params = nil)
+      params_hash = custom_params || params
       attrs = {}
       keys.each do |key|
-        if params[key].present? or (params.has_key?(key) and params[key] == false)
-          attrs[key] = params[key]
+        if params_hash[key].present? or (params_hash.has_key?(key) and params_hash[key] == false)
+          attrs[key] = params_hash[key]
         end
       end
       ActionController::Parameters.new(attrs).permit!
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 5c97fe1c88c1db0296042e0b5712ba573ff87ca2..dd7f24f32791fe5115572fbacfde8ead3db5ca1f 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -59,55 +59,6 @@ module API
         present paginate(merge_requests), with: Entities::MergeRequest
       end
 
-      # Show MR
-      #
-      # 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
-      #
-      get ":id/merge_request/:merge_request_id" do
-        merge_request = user_project.merge_requests.find(params[:merge_request_id])
-
-        authorize! :read_merge_request, merge_request
-
-        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:
-      #   id (required)               - The ID of a project
-      #   merge_request_id (required) - The ID of MR
-      #
-      # Example:
-      #   GET /projects/:id/merge_request/:merge_request_id/changes
-      #
-      get ':id/merge_request/:merge_request_id/changes' do
-        merge_request = user_project.merge_requests.
-          find(params[:merge_request_id])
-        authorize! :read_merge_request, merge_request
-        present merge_request, with: Entities::MergeRequestChanges
-      end
-
       # Create MR
       #
       # Parameters:
@@ -148,146 +99,206 @@ module API
         end
       end
 
-      # Update MR
+      # Routing "merge_request/:merge_request_id/..." is DEPRECATED and WILL BE REMOVED in version 9.0
+      # Use "merge_requests/:merge_request_id/..." instead.
       #
-      # Parameters:
-      #   id (required)               - The ID of a project
-      #   merge_request_id (required) - ID of MR
-      #   target_branch               - The target branch
-      #   assignee_id                 - Assignee user ID
-      #   title                       - Title of MR
-      #   state_event                 - Status of MR. (close|reopen|merge)
-      #   description                 - Description of MR
-      #   labels (optional)           - Labels for a MR as a comma-separated list
-      # Example:
-      #   PUT /projects/:id/merge_request/:merge_request_id
-      #
-      put ":id/merge_request/:merge_request_id" do
-        attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description]
-        merge_request = user_project.merge_requests.find(params[:merge_request_id])
-        authorize! :update_merge_request, merge_request
-
-        # Ensure source_branch is not specified
-        if params[:source_branch].present?
-          render_api_error!('Source branch cannot be changed', 400)
-        end
-
-        # Validate label names in advance
-        if (errors = validate_label_params(params)).any?
-          render_api_error!({ labels: errors }, 400)
-        end
-
-        merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
-
-        if merge_request.valid?
-          # Find or create labels and attach to issue
-          unless params[:labels].nil?
-            merge_request.remove_labels
-            merge_request.add_labels_by_names(params[:labels].split(","))
-          end
+      [":id/merge_request/:merge_request_id", ":id/merge_requests/:merge_request_id"].each do |path|
+        # Show MR
+        #
+        # Parameters:
+        #   id (required)               - The ID of a project
+        #   merge_request_id (required) - The ID of MR
+        #
+        # Example:
+        #   GET /projects/:id/merge_requests/:merge_request_id
+        #
+        get path do
+          merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+          authorize! :read_merge_request, merge_request
 
           present merge_request, with: Entities::MergeRequest
-        else
-          handle_merge_request_errors! merge_request.errors
         end
-      end
-
-      # 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
-      #   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])
-
-        # 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?
 
-        merge_request.check_if_can_be_merged
-
-        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
-          ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params).
-            execute(merge_request)
+        # Show MR commits
+        #
+        # Parameters:
+        #   id (required)               - The ID of a project
+        #   merge_request_id (required) - The ID of MR
+        #
+        # Example:
+        #   GET /projects/:id/merge_requests/:merge_request_id/commits
+        #
+        get "#{path}/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
 
-        present merge_request, with: Entities::MergeRequest
-      end
+        # Show MR changes
+        #
+        # Parameters:
+        #   id (required)               - The ID of a project
+        #   merge_request_id (required) - The ID of MR
+        #
+        # Example:
+        #   GET /projects/:id/merge_requests/:merge_request_id/changes
+        #
+        get "#{path}/changes" do
+          merge_request = user_project.merge_requests.
+            find(params[:merge_request_id])
+          authorize! :read_merge_request, merge_request
+          present merge_request, with: Entities::MergeRequestChanges
+        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])
+        # Update MR
+        #
+        # Parameters:
+        #   id (required)               - The ID of a project
+        #   merge_request_id (required) - ID of MR
+        #   target_branch               - The target branch
+        #   assignee_id                 - Assignee user ID
+        #   title                       - Title of MR
+        #   state_event                 - Status of MR. (close|reopen|merge)
+        #   description                 - Description of MR
+        #   labels (optional)           - Labels for a MR as a comma-separated list
+        # Example:
+        #   PUT /projects/:id/merge_requests/:merge_request_id
+        #
+        put path do
+          attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description]
+          merge_request = user_project.merge_requests.find(params[:merge_request_id])
+          authorize! :update_merge_request, merge_request
+
+          # Ensure source_branch is not specified
+          if params[:source_branch].present?
+            render_api_error!('Source branch cannot be changed', 400)
+          end
 
-        unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user)
+          # Validate label names in advance
+          if (errors = validate_label_params(params)).any?
+            render_api_error!({ labels: errors }, 400)
+          end
 
-        ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request)
-      end
+          merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
 
-      # Get a merge request's comments
-      #
-      # Parameters:
-      #   id (required)               - The ID of a project
-      #   merge_request_id (required) - ID of MR
-      # Examples:
-      #   GET /projects/:id/merge_request/:merge_request_id/comments
-      #
-      get ":id/merge_request/:merge_request_id/comments" do
-        merge_request = user_project.merge_requests.find(params[:merge_request_id])
+          if merge_request.valid?
+            # Find or create labels and attach to issue
+            unless params[:labels].nil?
+              merge_request.remove_labels
+              merge_request.add_labels_by_names(params[:labels].split(","))
+            end
 
-        authorize! :read_merge_request, merge_request
+            present merge_request, with: Entities::MergeRequest
+          else
+            handle_merge_request_errors! merge_request.errors
+          end
+        end
 
-        present paginate(merge_request.notes.fresh), with: Entities::MRNote
-      end
+        # 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
+        #   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_requests/:merge_request_id/merge
+        #
+        put "#{path}/merge" do
+          merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+          # 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?
+
+          merge_request.check_if_can_be_merged
+
+          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
+            ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params).
+              execute(merge_request)
+          end
 
-      # Post comment to merge request
-      #
-      # Parameters:
-      #   id (required)               - The ID of a project
-      #   merge_request_id (required) - ID of MR
-      #   note (required)             - Text of comment
-      # Examples:
-      #   POST /projects/:id/merge_request/:merge_request_id/comments
-      #
-      post ":id/merge_request/:merge_request_id/comments" do
-        required_attributes! [:note]
+          present merge_request, with: Entities::MergeRequest
+        end
 
-        merge_request = user_project.merge_requests.find(params[:merge_request_id])
+        # 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 "#{path}/cancel_merge_when_build_succeeds" do
+          merge_request = user_project.merge_requests.find(params[:merge_request_id])
 
-        authorize! :create_note, merge_request
+          unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user)
 
-        opts = {
-          note: params[:note],
-          noteable_type: 'MergeRequest',
-          noteable_id: merge_request.id
-        }
+          ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request)
+        end
 
-        note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+        # Duplicate. DEPRECATED and WILL BE REMOVED in 9.0.
+        # Use GET "/projects/:id/merge_requests/:merge_request_id/notes" instead
+        #
+        # Get a merge request's comments
+        #
+        # Parameters:
+        #   id (required)               - The ID of a project
+        #   merge_request_id (required) - ID of MR
+        # Examples:
+        #   GET /projects/:id/merge_requests/:merge_request_id/comments
+        #
+        get "#{path}/comments" do
+          merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+          authorize! :read_merge_request, merge_request
+
+          present paginate(merge_request.notes.fresh), with: Entities::MRNote
+        end
 
-        if note.save
-          present note, with: Entities::MRNote
-        else
-          render_api_error!("Failed to save note #{note.errors.messages}", 400)
+        # Duplicate. DEPRECATED and WILL BE REMOVED in 9.0.
+        # Use POST "/projects/:id/merge_requests/:merge_request_id/notes" instead
+        #
+        # Post comment to merge request
+        #
+        # Parameters:
+        #   id (required)               - The ID of a project
+        #   merge_request_id (required) - ID of MR
+        #   note (required)             - Text of comment
+        # Examples:
+        #   POST /projects/:id/merge_requests/:merge_request_id/comments
+        #
+        post "#{path}/comments" do
+          required_attributes! [:note]
+
+          merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+          authorize! :create_note, merge_request
+
+          opts = {
+            note: params[:note],
+            noteable_type: 'MergeRequest',
+            noteable_id: merge_request.id
+          }
+
+          note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+
+          if note.save
+            present note, with: Entities::MRNote
+          else
+            render_api_error!("Failed to save note #{note.errors.messages}", 400)
+          end
         end
       end
     end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 71bb342f8448cacd216acf55fb80a0a56265242b..1f991e600e38e2486a5b3b5e879bdbfc0a2593c2 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -187,7 +187,7 @@ module API
         else
           present @forked_project, with: Entities::Project,
                                    user_can_admin_project: can?(current_user, :admin_project, @forked_project)
-         end
+        end
       end
 
       # Update an existing project
@@ -246,7 +246,7 @@ module API
       #   DELETE /projects/:id
       delete ":id" do
         authorize! :remove_project, user_project
-        ::Projects::DestroyService.new(user_project, current_user, {}).execute
+        ::Projects::DestroyService.new(user_project, current_user, {}).pending_delete!
       end
 
       # Mark this project as forked from another
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 20bd4f7ee6e969c61eeab4ed87e27579768392f4..3637b1bac94850b6dbd2b28752df881d4a671f18 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -133,6 +133,7 @@ module Banzai
           next unless link && text
 
           link = CGI.unescape(link)
+          next unless link.force_encoding('UTF-8').valid_encoding?
           # Ignore ending punctionation like periods or commas
           next unless link == text && text =~ /\A#{pattern}/
 
@@ -170,6 +171,7 @@ module Banzai
 
           next unless link && text
           link = CGI.unescape(link)
+          next unless link.force_encoding('UTF-8').valid_encoding?
           next unless link && link =~ /\A#{pattern}\z/
 
           html = yield link, text
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 3f49d492f2fbf0a5d26a4da452abdd27d100d6c7..d1e11eedec3a969323bf167f0d0fbf79ca13b26e 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -43,6 +43,10 @@ module Banzai
         # Allow span elements
         whitelist[:elements].push('span')
 
+        # Allow abbr elements with title attribute
+        whitelist[:elements].push('abbr')
+        whitelist[:attributes]['abbr'] = %w(title)
+
         # Allow any protocol in `a` elements...
         whitelist[:protocols].delete('a')
 
diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb
index 5c347e432b4e98dea738a1f495608826edd0bf06..4e85d2c3c744003562cf27b9bc073f2992b552c9 100644
--- a/lib/ci/api/api.rb
+++ b/lib/ci/api/api.rb
@@ -25,7 +25,7 @@ module Ci
 
       format :json
 
-      helpers Helpers
+      helpers ::Ci::API::Helpers
       helpers ::API::Helpers
       helpers Gitlab::CurrentSettings
 
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 690bbf97a89a546f8885c153396f300c297faafd..416b0b5f0b4a3c42d1adbaf2c9bb619b127c66f5 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -13,13 +13,13 @@ module Ci
         post "register" do
           authenticate_runner!
           update_runner_last_contact
+          update_runner_info
           required_attributes! [:token]
           not_found! unless current_runner.active?
 
           build = Ci::RegisterBuildService.new.execute(current_runner)
 
           if build
-            update_runner_info
             present build, with: Entities::BuildDetails
           else
             not_found!
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 1c91204e98ca1c9331f0c597d4e8bb5933b3c7cd..199d62d9b8a1c2f08f2182fc1d1444b117f11ae5 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -34,10 +34,14 @@ module Ci
         @runner ||= Runner.find_by_token(params[:token].to_s)
       end
 
-      def update_runner_info
+      def get_runner_version_from_params
         return unless params["info"].present?
-        info = attributes_for_keys(["name", "version", "revision", "platform", "architecture"], params["info"])
-        current_runner.update(info)
+        attributes_for_keys(["name", "version", "revision", "platform", "architecture"], params["info"])
+      end
+
+      def update_runner_info
+        current_runner.assign_attributes(get_runner_version_from_params)
+        current_runner.save if current_runner.changed?
       end
 
       def max_artifacts_size
diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb
index bfc14fe7a6b5ac3f7ced3589b4d9ec7adcd5c127..192b1d18a51df02a300f5d39cf97e26af2270cbc 100644
--- a/lib/ci/api/runners.rb
+++ b/lib/ci/api/runners.rb
@@ -47,6 +47,7 @@ module Ci
           return forbidden! unless runner
 
           if runner.id
+            runner.update(get_runner_version_from_params)
             present runner, with: Entities::Runner
           else
             not_found!
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index bcdfd38d292f245b4fe4df8ed7dc93fb4a67dc57..1a3f662811a0a12467cd8cffe520b38bf64a842d 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -115,6 +115,10 @@ module Ci
       end
 
       if @cache
+        if @cache[:key] && !validate_string(@cache[:key])
+          raise ValidationError, "cache:key parameter should be a string"
+        end
+
         if @cache[:untracked] && !validate_boolean(@cache[:untracked])
           raise ValidationError, "cache:untracked parameter should be an boolean"
         end
@@ -198,6 +202,10 @@ module Ci
     end
 
     def validate_job_cache!(name, job)
+      if job[:cache][:key] && !validate_string(job[:cache][:key])
+        raise ValidationError, "#{name} job: cache:key parameter should be a string"
+      end
+
       if job[:cache][:untracked] && !validate_boolean(job[:cache][:untracked])
         raise ValidationError, "#{name} job: cache:untracked parameter should be an boolean"
       end
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 2355b3c6ddc62ec61bb9d536d97b21d8a9adc3ec..3f483847efaa14f6adf98502cf88bc6c92a8799f 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -13,12 +13,36 @@ module Gitlab
       end
 
       def execute
-        project_identifier = project.import_source
+        import_issues if has_issues?
 
-        return true unless client.project(project_identifier)["has_issues"]
+        true
+      rescue ActiveRecord::RecordInvalid => e
+        raise Projects::ImportService::Error.new, e.message
+      ensure
+        Gitlab::BitbucketImport::KeyDeleter.new(project).execute
+      end
 
-        #Issues && Comments
-        issues = client.issues(project_identifier)
+      private
+
+      def gl_user_id(project, bitbucket_id)
+        if bitbucket_id
+          user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s)
+          (user && user.id) || project.creator_id
+        else
+          project.creator_id
+        end
+      end
+
+      def identifier
+        project.import_source
+      end
+
+      def has_issues?
+        client.project(identifier)["has_issues"]
+      end
+
+      def import_issues
+        issues = client.issues(identifier)
 
         issues.each do |issue|
           body = ''
@@ -33,7 +57,7 @@ module Gitlab
           body = @formatter.author_line(author)
           body += issue["content"]
 
-          comments = client.issue_comments(project_identifier, issue["local_id"])
+          comments = client.issue_comments(identifier, issue["local_id"])
 
           if comments.any?
             body += @formatter.comments_header
@@ -56,20 +80,9 @@ module Gitlab
             author_id: gl_user_id(project, reporter)
           )
         end
-
-        true
+      rescue ActiveRecord::RecordInvalid => e
+        raise Projects::ImportService::Error, e.message
       end
-
-      private
-
-      def gl_user_id(project, bitbucket_id)
-        if bitbucket_id
-          user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s)
-          (user && user.id) || project.creator_id
-        else
-          project.creator_id
-        end
-       end
     end
   end
 end
diff --git a/lib/gitlab/blame.rb b/lib/gitlab/blame.rb
new file mode 100644
index 0000000000000000000000000000000000000000..313e6b9fc030a2fc4b27630c41dbb714bad19aec
--- /dev/null
+++ b/lib/gitlab/blame.rb
@@ -0,0 +1,54 @@
+module Gitlab
+  class Blame
+    attr_accessor :blob, :commit
+
+    def initialize(blob, commit)
+      @blob = blob
+      @commit = commit
+    end
+
+    def groups(highlight: true)
+      prev_sha = nil
+      groups = []
+      current_group = nil
+
+      i = 0
+      blame.each do |commit, line|
+        commit = Commit.new(commit, project)
+
+        sha = commit.sha
+        if prev_sha != sha
+          groups << current_group if current_group
+          current_group = { commit: commit, lines: [] }
+        end
+
+        line = highlighted_lines[i].html_safe if highlight
+        current_group[:lines] << line
+
+        prev_sha = sha
+        i += 1
+      end
+      groups << current_group if current_group
+
+      groups
+    end
+
+    private
+
+    def blame
+      @blame ||= Gitlab::Git::Blame.new(repository, @commit.id, @blob.path)
+    end
+
+    def highlighted_lines
+      @highlighted_lines ||= Gitlab::Highlight.highlight(@blob.name, @blob.data).lines
+    end
+
+    def project
+      commit.project
+    end
+
+    def repository
+      project.repository
+    end
+  end
+end
diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb
index 1344f5d120b32f6f17d84dda408ddebb04e00cea..f2020c82d40543d1baf443a66fa0eb44c63dade6 100644
--- a/lib/gitlab/ci/build/artifacts/metadata.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata.rb
@@ -13,8 +13,8 @@ module Gitlab
 
           attr_reader :file, :path, :full_version
 
-          def initialize(file, path)
-            @file, @path = file, path
+          def initialize(file, path, **opts)
+            @file, @path, @opts = file, path, opts
             @full_version = read_version
           end
 
@@ -52,7 +52,9 @@ module Gitlab
 
           def match_entries(gz)
             entries = {}
-            match_pattern = %r{^#{Regexp.escape(@path)}[^/]*/?$}
+
+            child_pattern = '[^/]*/?$' unless @opts[:recursive]
+            match_pattern = /^#{Regexp.escape(@path)}#{child_pattern}/
 
             until gz.eof? do
               begin
diff --git a/lib/gitlab/ci/build/artifacts/metadata/entry.rb b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
index 25b71fc3275e00cfd12c60d4bceae22d84e2303b..7f4c750b6fdaf8973296135c86be7a739764bc70 100644
--- a/lib/gitlab/ci/build/artifacts/metadata/entry.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
@@ -95,6 +95,13 @@ module Gitlab
           children.empty?
         end
 
+        def total_size
+          descendant_pattern = %r{^#{Regexp.escape(@path)}}
+          entries.sum do |path, entry|
+            (entry[:size] if path =~ descendant_pattern).to_i
+          end
+        end
+
         def to_s
           @path
         end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index ea054255820a8188134f60db94553a4336b815d3..a6b2f14521c408d42d5f54d44eadde28db2a9d53 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -4,11 +4,14 @@ module Gitlab
       key = :current_application_settings
 
       RequestStore.store[key] ||= begin
+        settings = nil
+
         if connect_to_db?
-          ApplicationSetting.current || ApplicationSetting.create_from_defaults
-        else
-          fake_application_settings
+          settings = ApplicationSetting.current
+          settings ||= ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration?
         end
+
+        settings || fake_application_settings
       end
     end
 
@@ -18,28 +21,32 @@ module Gitlab
         default_branch_protection: Settings.gitlab['default_branch_protection'],
         signup_enabled: Settings.gitlab['signup_enabled'],
         signin_enabled: Settings.gitlab['signin_enabled'],
+        twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
         gravatar_enabled: Settings.gravatar['enabled'],
         sign_in_text: Settings.extra['sign_in_text'],
         restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
         max_attachment_size: Settings.gitlab['max_attachment_size'],
         session_expire_delay: Settings.gitlab['session_expire_delay'],
-        import_sources: Settings.gitlab['import_sources'],
+        default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+        default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+        restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
+        import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'],
         shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
         max_artifacts_size: Settings.artifacts['max_size'],
+        require_two_factor_authentication: false,
+        two_factor_grace_period: 48
       )
     end
 
     private
 
     def connect_to_db?
-      use_db = if ENV['USE_DB'] == "false"
-                 false
-               else
-                 true
-               end
-
-      use_db && ActiveRecord::Base.connection.active? &&
-                ActiveRecord::Base.connection.table_exists?('application_settings')
+      # When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
+      active_db_connection = ActiveRecord::Base.connection.active? rescue false
+
+      ENV['USE_DB'] != 'false' &&
+      active_db_connection &&
+      ActiveRecord::Base.connection.table_exists?('application_settings')
 
     rescue ActiveRecord::NoDatabaseError
       false
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index 79061cd014181d0375b622431ae41c5379f493c6..a484177ae33f4c05a2abd1418211718f76654134 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -1,13 +1,22 @@
 module Gitlab
   module Diff
     class File
-      attr_reader :diff
+      attr_reader :diff, :diff_refs
 
       delegate :new_file, :deleted_file, :renamed_file,
         :old_path, :new_path, to: :diff, prefix: false
 
-      def initialize(diff)
+      def initialize(diff, diff_refs)
         @diff = diff
+        @diff_refs = diff_refs
+      end
+
+      def old_ref
+        diff_refs[0] if diff_refs
+      end
+
+      def new_ref
+        diff_refs[1] if diff_refs
       end
 
       # Array of Gitlab::DIff::Line objects
@@ -15,6 +24,14 @@ module Gitlab
         @lines ||= parser.parse(raw_diff.lines)
       end
 
+      def highlighted_diff_lines
+        Gitlab::Diff::Highlight.new(self).highlight
+      end
+
+      def parallel_diff_lines
+        Gitlab::Diff::ParallelDiff.new(self).parallelize
+      end
+
       def mode_changed?
         !!(diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode)
       end
diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9429b3ff88d76d8866862b27dcddd08d6ffc006a
--- /dev/null
+++ b/lib/gitlab/diff/highlight.rb
@@ -0,0 +1,77 @@
+module Gitlab
+  module Diff
+    class Highlight
+      attr_reader :diff_file, :diff_lines, :raw_lines
+
+      delegate :old_path, :new_path, :old_ref, :new_ref, to: :diff_file, prefix: :diff
+
+      def initialize(diff_lines)
+        if diff_lines.is_a?(Gitlab::Diff::File)
+          @diff_file = diff_lines
+          @diff_lines = @diff_file.diff_lines
+        else
+          @diff_lines = diff_lines
+        end
+        @raw_lines = @diff_lines.map(&:text)
+      end
+
+      def highlight
+        @diff_lines.map.with_index do |diff_line, i|
+          diff_line = diff_line.dup
+          # ignore highlighting for "match" lines
+          next diff_line if diff_line.type == 'match' || diff_line.type == 'nonewline'
+
+          rich_line = highlight_line(diff_line) || diff_line.text
+
+          if line_inline_diffs = inline_diffs[i]
+            rich_line = InlineDiffMarker.new(diff_line.text, rich_line).mark(line_inline_diffs)
+          end
+
+          diff_line.text = rich_line
+
+          diff_line
+        end
+      end
+
+      private
+
+      def highlight_line(diff_line)
+        return unless diff_file && diff_file.diff_refs
+
+        line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' '
+
+        case diff_line.type
+        when 'new', nil
+          rich_line = new_lines[diff_line.new_pos - 1]
+        when 'old'
+          rich_line = old_lines[diff_line.old_pos - 1]
+        end
+
+        # Only update text if line is found. This will prevent
+        # issues with submodules given the line only exists in diff content.
+        "#{line_prefix}#{rich_line}".html_safe if rich_line
+      end
+
+      def inline_diffs
+        @inline_diffs ||= InlineDiff.for_lines(@raw_lines)
+      end
+
+      def old_lines
+        return unless diff_file
+        @old_lines ||= Gitlab::Highlight.highlight_lines(*processing_args(:old))
+      end
+
+      def new_lines
+        return unless diff_file
+        @new_lines ||= Gitlab::Highlight.highlight_lines(*processing_args(:new))
+      end
+
+      def processing_args(version)
+        ref  = send("diff_#{version}_ref")
+        path = send("diff_#{version}_path")
+
+        [ref.project.repository, ref.id, path]
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb
new file mode 100644
index 0000000000000000000000000000000000000000..789c14518b003260f302c86ac14928ec69c52cb2
--- /dev/null
+++ b/lib/gitlab/diff/inline_diff.rb
@@ -0,0 +1,88 @@
+module Gitlab
+  module Diff
+    class InlineDiff
+      attr_accessor :old_line, :new_line, :offset
+
+      def self.for_lines(lines)
+        local_edit_indexes = self.find_local_edits(lines)
+
+        inline_diffs = []
+
+        local_edit_indexes.each do |index|
+          old_index = index
+          new_index = index + 1
+          old_line = lines[old_index]
+          new_line = lines[new_index]
+
+          old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs
+
+          inline_diffs[old_index] = old_diffs
+          inline_diffs[new_index] = new_diffs
+        end
+
+        inline_diffs
+      end
+
+      def initialize(old_line, new_line, offset: 0)
+        @old_line = old_line[offset..-1]
+        @new_line = new_line[offset..-1]
+        @offset = offset
+      end
+
+      def inline_diffs
+        # Skip inline diff if empty line was replaced with content
+        return if old_line == ""
+
+        lcp = longest_common_prefix(old_line, new_line)
+        lcs = longest_common_suffix(old_line[lcp..-1], new_line[lcp..-1])
+
+        lcp += offset
+        old_length = old_line.length + offset
+        new_length = new_line.length + offset
+
+        old_diff_range = lcp..(old_length - lcs - 1)
+        new_diff_range = lcp..(new_length - lcs - 1)
+
+        old_diffs = [old_diff_range] if old_diff_range.begin <= old_diff_range.end
+        new_diffs = [new_diff_range] if new_diff_range.begin <= new_diff_range.end
+
+        [old_diffs, new_diffs]
+      end
+
+      private
+
+      def self.find_local_edits(lines)
+        line_prefixes = lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' }
+        joined_line_prefixes = " #{line_prefixes.join} "
+
+        offset = 0
+        local_edit_indexes = []
+        while index = joined_line_prefixes.index(" -+ ", offset)
+          local_edit_indexes << index
+          offset = index + 1
+        end
+
+        local_edit_indexes
+      end
+
+      def longest_common_prefix(a, b)
+        max_length = [a.length, b.length].max
+
+        length = 0
+        (0..max_length - 1).each do |pos|
+          old_char = a[pos]
+          new_char = b[pos]
+
+          break if old_char != new_char
+          length += 1
+        end
+
+        length
+      end
+
+      def longest_common_suffix(a, b)
+        longest_common_prefix(a.reverse, b.reverse)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/diff/inline_diff_marker.rb b/lib/gitlab/diff/inline_diff_marker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dccb717e95dfbfb3a535221301204205096cab13
--- /dev/null
+++ b/lib/gitlab/diff/inline_diff_marker.rb
@@ -0,0 +1,115 @@
+module Gitlab
+  module Diff
+    class InlineDiffMarker
+      attr_accessor :raw_line, :rich_line
+
+      def initialize(raw_line, rich_line = raw_line)
+        @raw_line = raw_line
+        @rich_line = ERB::Util.html_escape(rich_line)
+      end
+
+      def mark(line_inline_diffs)
+        return rich_line unless line_inline_diffs
+
+        marker_ranges = []
+        line_inline_diffs.each do |inline_diff_range|
+          # Map the inline-diff range based on the raw line to character positions in the rich line
+          inline_diff_positions = position_mapping[inline_diff_range].flatten
+          # Turn the array of character positions into ranges
+          marker_ranges.concat(collapse_ranges(inline_diff_positions))
+        end
+
+        offset = 0
+        # Mark each range
+        marker_ranges.each_with_index do |range, i|
+          class_names = ["idiff"]
+          class_names << "left"   if i == 0
+          class_names << "right"  if i == marker_ranges.length - 1
+
+          offset = insert_around_range(rich_line, range, "<span class='#{class_names.join(" ")}'>", "</span>", offset)
+        end
+
+        rich_line.html_safe
+      end
+
+      private
+
+      # Mapping of character positions in the raw line, to the rich (highlighted) line
+      def position_mapping
+        @position_mapping ||= begin
+          mapping = []
+          rich_pos = 0
+          (0..raw_line.length).each do |raw_pos|
+            rich_char = rich_line[rich_pos]
+
+            # The raw and rich lines are the same except for HTML tags,
+            # so skip over any `<...>` segment
+            while rich_char == '<'
+              until rich_char == '>'
+                rich_pos += 1
+                rich_char = rich_line[rich_pos]
+              end
+
+              rich_pos += 1
+              rich_char = rich_line[rich_pos]
+            end
+
+            # multi-char HTML entities in the rich line correspond to a single character in the raw line
+            if rich_char == '&'
+              multichar_mapping = [rich_pos]
+              until rich_char == ';'
+                rich_pos += 1
+                multichar_mapping << rich_pos
+                rich_char = rich_line[rich_pos]
+              end
+
+              mapping[raw_pos] = multichar_mapping
+            else
+              mapping[raw_pos] = rich_pos
+            end
+
+            rich_pos += 1
+          end
+
+          mapping
+        end
+      end
+
+      # Takes an array of integers, and returns an array of ranges covering the same integers
+      def collapse_ranges(positions)
+        return [] if positions.empty?
+        ranges = []
+
+        start = prev = positions[0]
+        range = start..prev
+        positions[1..-1].each do |pos|
+          if pos == prev + 1
+            range = start..pos
+            prev = pos
+          else
+            ranges << range
+            start = prev = pos
+            range = start..prev
+          end
+        end
+        ranges << range
+
+        ranges
+      end
+
+      # Inserts tags around the characters identified by the given range
+      def insert_around_range(text, range, before, after, offset = 0)
+        # Just to be sure
+        return offset if offset + range.end + 1 > text.length
+
+        text.insert(offset + range.begin, before)
+        offset += before.length
+
+        text.insert(offset + range.end + 1, after)
+        offset += after.length
+
+        offset
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb
index 0072194606e69b5c9da9f086c80bd7fa2a630e16..03730b435adc5fdceaf404198ee07ccf9da58ace 100644
--- a/lib/gitlab/diff/line.rb
+++ b/lib/gitlab/diff/line.rb
@@ -1,7 +1,8 @@
 module Gitlab
   module Diff
     class Line
-      attr_reader :type, :text, :index, :old_pos, :new_pos
+      attr_reader :type, :index, :old_pos, :new_pos
+      attr_accessor :text
 
       def initialize(text, type, index, old_pos, new_pos)
         @text, @type, @index = text, type, index
diff --git a/lib/gitlab/diff/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb
new file mode 100644
index 0000000000000000000000000000000000000000..74f9b3c050a616a62cb9a4c1e2bafb0b17734c90
--- /dev/null
+++ b/lib/gitlab/diff/parallel_diff.rb
@@ -0,0 +1,119 @@
+module Gitlab
+  module Diff
+    class ParallelDiff
+      attr_accessor :diff_file
+
+      def initialize(diff_file)
+        @diff_file = diff_file
+      end
+
+      def parallelize
+        lines = []
+        skip_next = false
+
+        highlighted_diff_lines = diff_file.highlighted_diff_lines
+        highlighted_diff_lines.each do |line|
+          full_line = line.text
+          type = line.type
+          line_code = generate_line_code(diff_file.file_path, line)
+          line_new = line.new_pos
+          line_old = line.old_pos
+
+          next_line = diff_file.next_line(line.index)
+
+          if next_line
+            next_line = highlighted_diff_lines[next_line.index]
+            next_line_code = generate_line_code(diff_file.file_path, next_line)
+            next_type = next_line.type
+            next_line = next_line.text
+          end
+
+          case type
+          when 'match', nil
+            # line in the right panel is the same as in the left one
+            lines << {
+              left: {
+                type:       type,
+                number:     line_old,
+                text:       full_line,
+                line_code:  line_code,
+              },
+              right: {
+                type:       type,
+                number:     line_new,
+                text:       full_line,
+                line_code:  line_code
+              }
+            }
+          when 'old'
+            case next_type
+            when 'new'
+              # Left side has text removed, right side has text added
+              lines << {
+                left: {
+                  type:       type,
+                  number:     line_old,
+                  text:       full_line,
+                  line_code:  line_code,
+                },
+                right: {
+                  type:       next_type,
+                  number:     line_new,
+                  text:       next_line,
+                  line_code:  next_line_code
+                }
+              }
+              skip_next = true
+            when 'old', 'nonewline', nil
+              # Left side has text removed, right side doesn't have any change
+              # No next line code, no new line number, no new line text
+              lines << {
+                left: {
+                  type:       type,
+                  number:     line_old,
+                  text:       full_line,
+                  line_code:  line_code,
+                },
+                right: {
+                  type:       next_type,
+                  number:     nil,
+                  text:       "",
+                  line_code:  nil
+                }
+              }
+            end
+          when 'new'
+            if skip_next
+              # Change has been already included in previous line so no need to do it again
+              skip_next = false
+              next
+            else
+              # Change is only on the right side, left side has no change
+              lines << {
+                left: {
+                  type:       nil,
+                  number:     nil,
+                  text:       "",
+                  line_code:  line_code,
+                },
+                right: {
+                  type:       type,
+                  number:     line_new,
+                  text:       full_line,
+                  line_code:  line_code
+                }
+              }
+            end
+          end
+        end
+        lines
+      end
+
+      private
+
+      def generate_line_code(file_path, line)
+        Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
index 516e59b87a38293bd87ca0c33fcb1b3c76d4a5f3..3666063bf8b81c25b30de52250f9600fe5afcdf7 100644
--- a/lib/gitlab/diff/parser.rb
+++ b/lib/gitlab/diff/parser.rb
@@ -11,13 +11,10 @@ module Gitlab
         line_new = 1
         type = nil
 
-        lines_arr = ::Gitlab::InlineDiff.processing lines
-
-        lines_arr.each do |line|
+        @lines.each do |line|
           next if filename?(line)
 
-          full_line = html_escape(line.gsub(/\n/, ''))
-          full_line = ::Gitlab::InlineDiff.replace_markers full_line
+          full_line = line.gsub(/\n/, '')
 
           if line.match(/^@@ -/)
             type = "match"
@@ -29,6 +26,10 @@ module Gitlab
             lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
             line_obj_index += 1
             next
+          elsif line[0] == '\\'
+            type = 'nonewline'
+            lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
+            line_obj_index += 1
           else
             type = identification_type(line)
             lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
@@ -36,10 +37,13 @@ module Gitlab
           end
 
 
-          if line[0] == "+"
+          case line[0]
+          when "+"
             line_new += 1
-          elsif line[0] == "-"
+          when "-"
             line_old += 1
+          when "\\"
+            # No increment
           else
             line_new += 1
             line_old += 1
@@ -62,19 +66,15 @@ module Gitlab
       end
 
       def identification_type(line)
-        if line[0] == "+"
+        case line[0]
+        when "+"
           "new"
-        elsif line[0] == "-"
+        when "-"
           "old"
         else
           nil
         end
       end
-
-      def html_escape(str)
-        replacements = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
-        str.gsub(/[&"'><]/, replacements)
-      end
     end
   end
 end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 18929b9113b89dbea4e128a13c239544013b5b8e..e2a85f2982593dbfb118d619dc3a320109887168 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -35,8 +35,8 @@ module Gitlab
         end
 
         true
-      rescue ActiveRecord::RecordInvalid
-        false
+      rescue ActiveRecord::RecordInvalid => e
+        raise Projects::ImportService::Error, e.message
       end
 
       def import_pull_requests
@@ -53,8 +53,8 @@ module Gitlab
         end
 
         true
-      rescue ActiveRecord::RecordInvalid
-        false
+      rescue ActiveRecord::RecordInvalid => e
+        raise Projects::ImportService::Error, e.message
       end
 
       def import_comments(issue_number, noteable)
@@ -82,8 +82,15 @@ module Gitlab
         end
 
         true
-      rescue Gitlab::Shell::Error
-        false
+      rescue Gitlab::Shell::Error => e
+        # GitHub error message when the wiki repo has not been created,
+        # this means that repo has wiki enabled, but have no pages. So,
+        # we can skip the import.
+        if e.message !~ /repository not exported/
+          raise Projects::ImportService::Error, e.message
+        else
+          true
+        end
       end
     end
   end
diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb
index b7c47958cc7f58a4f6a0a5efb44e770964934e50..f96fed0f5cfb1359418a64bae9ce86d04e1a5b81 100644
--- a/lib/gitlab/github_import/pull_request_formatter.rb
+++ b/lib/gitlab/github_import/pull_request_formatter.rb
@@ -18,7 +18,7 @@ module Gitlab
       end
 
       def cross_project?
-        source_repo.fork == true
+        source_repo.id != target_repo.id
       end
 
       def number
@@ -73,6 +73,10 @@ module Gitlab
         project
       end
 
+      def target_repo
+        raw_data.base.repo
+      end
+
       def target_branch
         target_project.repository.find_branch(raw_data.base.ref)
       end
diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cac7644232163d4d70033ed2952c2bce3540d9b0
--- /dev/null
+++ b/lib/gitlab/highlight.rb
@@ -0,0 +1,39 @@
+module Gitlab
+  class Highlight
+    def self.highlight(blob_name, blob_content, nowrap: true)
+      new(blob_name, blob_content, nowrap: nowrap).highlight(blob_content, continue: false)
+    end
+
+    def self.highlight_lines(repository, ref, file_name)
+      blob = repository.blob_at(ref, file_name)
+      return [] unless blob
+
+      blob.load_all_data!(repository)
+      highlight(file_name, blob.data).lines.map!(&:html_safe)
+    end
+
+    def initialize(blob_name, blob_content, nowrap: true)
+      @formatter = rouge_formatter(nowrap: nowrap)
+      @lexer = Rouge::Lexer.guess(filename: blob_name, source: blob_content).new rescue Rouge::Lexers::PlainText
+    end
+
+    def highlight(text, continue: true)
+      @formatter.format(@lexer.lex(text, continue: continue)).html_safe
+    rescue
+      @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
+    end
+
+    private
+
+    def rouge_formatter(options = {})
+      options = options.reverse_merge(
+        nowrap: true,
+        cssclass: 'code highlight',
+        lineanchors: true,
+        lineanchorsid: 'LC'
+      )
+
+      Rouge::Formatters::HTMLGitlab.new(options)
+    end
+  end
+end
diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb
deleted file mode 100644
index 44507bde25deaef0e8f16adab6ca7af2670c99d7..0000000000000000000000000000000000000000
--- a/lib/gitlab/inline_diff.rb
+++ /dev/null
@@ -1,104 +0,0 @@
-module Gitlab
-  class InlineDiff
-    class << self
-
-      START  = "#!idiff-start!#"
-      FINISH = "#!idiff-finish!#"
-
-      def processing(diff_arr)
-        indexes = _indexes_of_changed_lines diff_arr
-
-        indexes.each do |index|
-          first_line = diff_arr[index+1]
-          second_line = diff_arr[index+2]
-
-          # Skip inline diff if empty line was replaced with content
-          next if first_line == "-\n"
-
-          first_token = find_first_token(first_line, second_line)
-          apply_first_token(diff_arr, index, first_token)
-
-          last_token = find_last_token(first_line, second_line, first_token)
-          apply_last_token(diff_arr, index, last_token)
-        end
-
-        diff_arr
-      end
-
-      def apply_first_token(diff_arr, index, first_token)
-        start = first_token + START
-
-        if first_token.empty?
-          # In case if we remove string of spaces in commit
-          diff_arr[index+1].sub!("-", "-" => "-#{START}")
-          diff_arr[index+2].sub!("+", "+" => "+#{START}")
-        else
-          diff_arr[index+1].sub!(first_token, first_token => start)
-          diff_arr[index+2].sub!(first_token, first_token => start)
-        end
-      end
-
-      def apply_last_token(diff_arr, index, last_token)
-        # This is tricky: escape backslashes so that `sub` doesn't interpret them
-        # as backreferences. Regexp.escape does NOT do the right thing.
-        replace_token = FINISH + last_token.gsub(/\\/, '\&\&')
-        diff_arr[index+1].sub!(/#{Regexp.escape(last_token)}$/, replace_token)
-        diff_arr[index+2].sub!(/#{Regexp.escape(last_token)}$/, replace_token)
-      end
-
-      def find_first_token(first_line, second_line)
-        max_length = [first_line.size, second_line.size].max
-        first_the_same_symbols = 0
-
-        (0..max_length + 1).each do |i|
-          first_the_same_symbols = i - 1
-
-          if first_line[i] != second_line[i] && i > 0
-            break
-          end
-        end
-
-        first_line[0..first_the_same_symbols][1..-1]
-      end
-
-      def find_last_token(first_line, second_line, first_token)
-        max_length = [first_line.size, second_line.size].max
-        last_the_same_symbols = 0
-
-        (1..max_length + 1).each do |i|
-          last_the_same_symbols = -i
-          shortest_line = second_line.size > first_line.size ? first_line : second_line
-
-          if (first_line[-i] != second_line[-i]) || "#{first_token}#{START}".size == shortest_line[1..-i].size
-            break
-          end
-        end
-
-        last_the_same_symbols += 1
-        first_line[last_the_same_symbols..-1]
-      end
-
-      def _indexes_of_changed_lines(diff_arr)
-        chain_of_first_symbols = ""
-        diff_arr.each_with_index do |line, i|
-          chain_of_first_symbols += line[0]
-        end
-        chain_of_first_symbols.gsub!(/[^\-\+]/, "#")
-
-        offset = 0
-        indexes = []
-        while index = chain_of_first_symbols.index("#-+#", offset)
-          indexes << index
-          offset = index + 1
-        end
-        indexes
-      end
-
-      def replace_markers(line)
-        line.gsub!(START, "<span class='idiff'>")
-        line.gsub!(FINISH, "</span>")
-        line
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
index d9fce2e6758b46f1bc357cc03bf6b788f9981f88..face1921d2ea6c0a1394ed2b933979d5c78d1157 100644
--- a/lib/gitlab/metrics/instrumentation.rb
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -106,20 +106,36 @@ module Gitlab
         if type == :instance
           target = mod
           label  = "#{mod.name}##{name}"
+          method = mod.instance_method(name)
         else
           target = mod.singleton_class
           label  = "#{mod.name}.#{name}"
+          method = mod.method(name)
+        end
+
+        # Some code out there (e.g. the "state_machine" Gem) checks the arity of
+        # a method to make sure it only passes arguments when the method expects
+        # any. If we were to always overwrite a method to take an `*args`
+        # signature this would break things. As a result we'll make sure the
+        # generated method _only_ accepts regular arguments if the underlying
+        # method also accepts them.
+        if method.arity == 0
+          args_signature = '&block'
+        else
+          args_signature = '*args, &block'
         end
 
+        send_signature = "__send__(#{alias_name.inspect}, #{args_signature})"
+
         target.class_eval <<-EOF, __FILE__, __LINE__ + 1
           alias_method #{alias_name.inspect}, #{name.inspect}
 
-          def #{name}(*args, &block)
+          def #{name}(#{args_signature})
             trans = Gitlab::Metrics::Instrumentation.transaction
 
             if trans
               start    = Time.now
-              retval   = __send__(#{alias_name.inspect}, *args, &block)
+              retval   = #{send_signature}
               duration = (Time.now - start) * 1000.0
 
               if duration >= Gitlab::Metrics.method_call_threshold
@@ -132,7 +148,7 @@ module Gitlab
 
               retval
             else
-              __send__(#{alias_name.inspect}, *args, &block)
+              #{send_signature}
             end
           end
         EOF
diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb
index 938219efdb29d6cf75e2d84dad062e7a267243ec..38364a0b151289a753e1a7a3d610f228fc91f4c4 100644
--- a/lib/gitlab/snippet_search_results.rb
+++ b/lib/gitlab/snippet_search_results.rb
@@ -1,5 +1,7 @@
 module Gitlab
   class SnippetSearchResults < SearchResults
+    include SnippetsHelper
+
     attr_reader :limit_snippet_ids
 
     def initialize(limit_snippet_ids, query)
@@ -47,85 +49,5 @@ module Gitlab
     def default_scope
       'snippet_blobs'
     end
-
-    # Get an array of line numbers surrounding a matching
-    # line, bounded by min/max.
-    #
-    # @returns Array of line numbers
-    def bounded_line_numbers(line, min, max)
-      lower = line - surrounding_lines > min ? line - surrounding_lines : min
-      upper = line + surrounding_lines < max ? line + surrounding_lines : max
-      (lower..upper).to_a
-    end
-
-    # Returns a sorted set of lines to be included in a snippet preview.
-    # This ensures matching adjacent lines do not display duplicated
-    # surrounding code.
-    #
-    # @returns Array, unique and sorted.
-    def matching_lines(lined_content)
-      used_lines = []
-      lined_content.each_with_index do |line, line_number|
-        used_lines.concat bounded_line_numbers(
-          line_number,
-          0,
-          lined_content.size
-        ) if line.include?(query)
-      end
-
-      used_lines.uniq.sort
-    end
-
-    # 'Chunkify' entire snippet.  Splits the snippet data into matching lines +
-    # surrounding_lines() worth of unmatching lines.
-    #
-    # @returns a hash with {snippet_object, snippet_chunks:{data,start_line}}
-    def chunk_snippet(snippet)
-      lined_content = snippet.content.split("\n")
-      used_lines = matching_lines(lined_content)
-
-      snippet_chunk = []
-      snippet_chunks = []
-      snippet_start_line = 0
-      last_line = -1
-
-      # Go through each used line, and add consecutive lines as a single chunk
-      # to the snippet chunk array.
-      used_lines.each do |line_number|
-        if last_line < 0
-          # Start a new chunk.
-          snippet_start_line = line_number
-          snippet_chunk << lined_content[line_number]
-        elsif last_line == line_number - 1
-          # Consecutive line, continue chunk.
-          snippet_chunk << lined_content[line_number]
-        else
-          # Non-consecutive line, add chunk to chunk array.
-          snippet_chunks << {
-            data: snippet_chunk.join("\n"),
-            start_line: snippet_start_line + 1
-          }
-
-          # Start a new chunk.
-          snippet_chunk = [lined_content[line_number]]
-          snippet_start_line = line_number
-        end
-        last_line = line_number
-      end
-      # Add final chunk to chunk array
-      snippet_chunks << {
-        data: snippet_chunk.join("\n"),
-        start_line: snippet_start_line + 1
-      }
-
-      # Return snippet with chunk array
-      { snippet_object: snippet, snippet_chunks: snippet_chunks }
-    end
-
-    # Defines how many unmatching lines should be
-    # included around the matching lines in a snippet
-    def surrounding_lines
-      3
-    end
   end
 end
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index c5f07c8b508a948796901b0cf6bcc5024360ba71..1633891c8a0749493e2f45175d3cd708a84f9ca7 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -38,6 +38,7 @@ 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_dir=$(cd $app_root/../gitlab-workhorse && pwd)
 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 -authSocket $rails_socket -documentRoot $app_root/public"
 gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
@@ -233,10 +234,12 @@ start_gitlab() {
   if [ "$gitlab_workhorse_status" = "0" ]; then
     echo "The gitlab-workhorse is already running with pid $spid, not restarting"
   else
-    # No need to remove a socket, gitlab-workhorse does this itself
+    # No need to remove a socket, gitlab-workhorse does this itself.
+    # Because gitlab-workhorse has multiple executables we need to fix
+    # the PATH.
     $app_root/bin/daemon_with_pidfile $gitlab_workhorse_pid_path  \
-      $app_root/../gitlab-workhorse/gitlab-workhorse \
-        $gitlab_workhorse_options \
+      /usr/bin/env PATH=$gitlab_workhorse_dir:$PATH \
+        gitlab-workhorse $gitlab_workhorse_options \
       >> $gitlab_workhorse_log 2>&1 &
   fi
 
diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example
index 1937ca582b0548939c42d2aca1bab39b45357b2a..4e6e56ac2db5ad3f5e065fd41506bb649bd15a2a 100755
--- a/lib/support/init.d/gitlab.default.example
+++ b/lib/support/init.d/gitlab.default.example
@@ -30,6 +30,9 @@ web_server_pid_path="$pid_path/unicorn.pid"
 # The default is "$pid_path/sidekiq.pid"
 sidekiq_pid_path="$pid_path/sidekiq.pid"
 
+# The directory where the gitlab-workhorse binaries are. Usually
+# /home/git/gitlab-workhorse .
+gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse && pwd)
 gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
 # The -listenXxx settings determine where gitlab-workhorse
 # listens for connections from NGINX. To listen on localhost:8181, write
diff --git a/scripts/ci/prepare_build.sh b/scripts/ci/prepare_build.sh
deleted file mode 100755
index 864a683a1bdf07ceff0457032bcf4351c0010134..0000000000000000000000000000000000000000
--- a/scripts/ci/prepare_build.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-if [ -f /.dockerinit ]; then
-    export FLAGS=(--deployment --path /cache)
-
-    apt-get update -qq
-    apt-get install -y -qq nodejs
-
-    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
-
-    cp config/database.yml.mysql config/database.yml
-    sed -i "s/username:.*/username: root/g" config/database.yml
-    sed -i "s/password:.*/password:/g" config/database.yml
-    sed -i "s/# socket:.*/host: mysql/g" config/database.yml
-else
-    export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
-
-    cp config/database.yml.mysql config/database.yml
-    sed -i "s/username\:.*$/username\: runner/" config/database.yml
-    sed -i "s/password\:.*$/password\: 'password'/" config/database.yml
-    sed -i "s/gitlab_ci_test/gitlab_ci_test_$((RANDOM/5000))/" config/database.yml
-fi
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 119cc90fc1ebde8edb6aa5d5289aae77ac35cffb..5987988dc8e5f1d9d1240b430b90c36c3ff4d5ae 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -1,10 +1,16 @@
 #!/bin/bash
+
 if [ -f /.dockerinit ]; then
-    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
+    # Docker runners use `/cache` folder which is persisted every build
+    if [ ! -e /cache/phantomjs_1.9.8-0jessie_amd64.deb ]; then
+        wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
+        mv phantomjs_1.9.8-0jessie_amd64.deb /cache
+    fi
+    dpkg -i /cache/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
+    apt-get -o dir::cache::archives="/cache/apt" install -y -qq --force-yes \
+        libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client unzip
 
     cp config/database.yml.mysql config/database.yml
     sed -i 's/username:.*/username: root/g' config/database.yml
@@ -13,8 +19,8 @@ if [ -f /.dockerinit ]; then
 
     cp config/resque.yml.example config/resque.yml
     sed -i 's/localhost/redis/g' config/resque.yml
-    FLAGS=(--deployment --path /cache)
-    export FLAGS
+
+    export FLAGS=(--path /cache)
 else
     export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
     cp config/database.yml.mysql config/database.yml
diff --git a/spec/controllers/blame_controller_spec.rb b/spec/controllers/blame_controller_spec.rb
index 3ad4d5fc0a881d51ef5739fed55732c7edb387e4..25f06299a29e0eb50ab3da1765d0f193d51d965d 100644
--- a/spec/controllers/blame_controller_spec.rb
+++ b/spec/controllers/blame_controller_spec.rb
@@ -24,20 +24,6 @@ describe Projects::BlameController do
     context "valid file" do
       let(:id) { 'master/files/ruby/popen.rb' }
       it { is_expected.to respond_with(:success) }
-
-      it 'groups blames properly' do
-        blame = assigns(:blame)
-        # Sanity check a few items
-        expect(blame.count).to eq(18)
-        expect(blame[0][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
-        expect(blame[0][:lines]).to eq(["require 'fileutils'", "require 'open3'", ""])
-
-        expect(blame[1][:commit].sha).to eq('874797c3a73b60d2187ed6e2fcabd289ff75171e')
-        expect(blame[1][:lines]).to eq(["module Popen", "  extend self"])
-
-        expect(blame[-1][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
-        expect(blame[-1][:lines]).to eq(["  end", "end"])
-      end
     end
   end
 end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..938e97298b64e9ba213ec8b7d3b3364f273e3fb8
--- /dev/null
+++ b/spec/controllers/groups_controller_spec.rb
@@ -0,0 +1,23 @@
+require 'rails_helper'
+
+describe GroupsController do
+  describe 'GET index' do
+    context 'as a user' do
+      it 'redirects to Groups Dashboard' do
+        sign_in(create(:user))
+
+        get :index
+
+        expect(response).to redirect_to(dashboard_groups_path)
+      end
+    end
+
+    context 'as a guest' do
+      it 'redirects to Explore Groups' do
+        get :index
+
+        expect(response).to redirect_to(explore_groups_path)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..85d1d1e052475c481f8114e8e48b066dd189502f
--- /dev/null
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -0,0 +1,109 @@
+require 'spec_helper'
+
+describe Projects::ImportsController do
+  let(:user) { create(:user) }
+
+  describe 'GET #show' do
+    context 'when repository does not exists' do
+      let(:project) { create(:empty_project) }
+
+      before do
+        sign_in(user)
+        project.team << [user, :master]
+      end
+
+      it 'renders template' do
+        get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+        expect(response).to render_template :show
+      end
+
+      it 'sets flash.now if params is present' do
+        get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { notice_now: 'Started' }
+
+        expect(flash.now[:notice]).to eq 'Started'
+      end
+    end
+
+    context 'when repository exists' do
+      let(:project) { create(:project_empty_repo, import_url: 'https://github.com/vim/vim.git') }
+
+      before do
+        sign_in(user)
+        project.team << [user, :master]
+      end
+
+      context 'when import is in progress' do
+        before do
+          project.update_attribute(:import_status, :started)
+        end
+
+        it 'renders template' do
+          get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+          expect(response).to render_template :show
+        end
+
+        it 'sets flash.now if params is present' do
+          get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { notice_now: 'In progress' }
+
+          expect(flash.now[:notice]).to eq 'In progress'
+        end
+      end
+
+      context 'when import failed' do
+        before do
+          project.update_attribute(:import_status, :failed)
+        end
+
+        it 'redirects to new_namespace_project_import_path' do
+          get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+          expect(response).to redirect_to new_namespace_project_import_path(project.namespace, project)
+        end
+      end
+
+      context 'when import finished' do
+        before do
+          project.update_attribute(:import_status, :finished)
+        end
+
+        context 'when project is a fork' do
+          it 'redirects to namespace_project_path' do
+            allow_any_instance_of(Project).to receive(:forked?).and_return(true)
+
+            get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+            expect(flash[:notice]).to eq 'The project was successfully forked.'
+            expect(response).to redirect_to namespace_project_path(project.namespace, project)
+          end
+        end
+
+        context 'when project is external' do
+          it 'redirects to namespace_project_path' do
+            get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+            expect(flash[:notice]).to eq 'The project was successfully imported.'
+            expect(response).to redirect_to namespace_project_path(project.namespace, project)
+          end
+        end
+
+        context 'when continue params is present' do
+          let(:params) do
+            {
+              to: namespace_project_path(project.namespace, project),
+              notice: 'Finished'
+            }
+          end
+
+          it 'redirects to params[:to]' do
+            get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: params
+
+            expect(flash[:notice]).to eq params[:notice]
+            expect(response).to redirect_to params[:to]
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 8898b71e2a3724182a4e70d4e237102ad97560f8..b7c2b32cb13d07a7f6e0c946f7255816d76a9de9 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -1,11 +1,15 @@
 FactoryGirl.define do
   factory :commit_status, class: CommitStatus do
-    started_at 'Di 29. Okt 09:51:28 CET 2013'
-    finished_at 'Di 29. Okt 09:53:28 CET 2013'
     name 'default'
     status 'success'
     description 'commit status'
     commit factory: :ci_commit_with_one_job
+    started_at 'Tue, 26 Jan 2016 08:21:42 +0100'
+    finished_at 'Tue, 26 Jan 2016 08:23:42 +0100'
+
+    after(:build) do |build, evaluator|
+      build.project = build.commit.project
+    end
 
     factory :generic_commit_status, class: GenericCommitStatus do
       name 'generic'
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index fe7f07f5b75017293b4772d71c7ecd746a2da46c..5a62da10619ea54e2c4e7d8b078947cb84b7c944 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -16,83 +16,104 @@ describe 'Commits' do
       FactoryGirl.create :ci_commit, project: project, sha: project.commit.sha
     end
 
-    let!(:build) { FactoryGirl.create :ci_build, commit: commit }
+    context 'commit status is Generic Commit Status' do
+      let!(:status) { FactoryGirl.create :generic_commit_status, commit: commit }
 
-    describe 'Project commits' do
-      before do
-        visit namespace_project_commits_path(project.namespace, project, :master)
-      end
+      describe 'Commit builds' do
+        before do
+          visit ci_status_path(commit)
+        end
 
-      it 'should show build status' do
-        page.within("//li[@id='commit-#{commit.short_sha}']") do
-          expect(page).to have_css(".ci-status-link")
+        it { expect(page).to have_content commit.sha[0..7] }
+
+        it 'contains generic commit status build' do
+          page.within('.table-holder') do
+            expect(page).to have_content "##{status.id}" # build id
+            expect(page).to have_content 'generic'       # build name
+          end
         end
       end
     end
 
-    describe 'Commit builds' do
-      before do
-        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 }
-    end
+    context 'commit status is Ci Build' do
+      let!(:build) { FactoryGirl.create :ci_build, commit: commit }
 
-    context 'Download artifacts' do
-      let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
+      describe 'Project commits' do
+        before do
+          visit namespace_project_commits_path(project.namespace, project, :master)
+        end
 
-      before do
-        build.update_attributes(artifacts_file: artifacts_file)
+        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
 
-      it do
-        visit ci_status_path(commit)
-        click_on 'Download artifacts'
-        expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
-      end
-    end
+      describe 'Commit builds' do
+        before do
+          visit ci_status_path(commit)
+        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'
+        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
-    end
 
-    describe 'Cancel build' do
-      it 'cancels build' do
-        visit ci_status_path(commit)
-        click_on 'Cancel'
-        expect(page).to have_content 'canceled'
-      end
-    end
+      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)
+        end
 
-    describe '.gitlab-ci.yml not found warning' do
-      context 'ci builds enabled' do
-        it "does not show warning" do
+        it do
           visit ci_status_path(commit)
-          expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+          click_on 'Download artifacts'
+          expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
         end
+      end
 
-        it 'shows warning' do
-          stub_ci_commit_yaml_file(nil)
+      describe 'Cancel all builds' do
+        it 'cancels commit' do
           visit ci_status_path(commit)
-          expect(page).to have_content '.gitlab-ci.yml not found in this commit'
+          click_on 'Cancel running'
+          expect(page).to have_content 'canceled'
         end
       end
 
-      context 'ci builds disabled' do
-        before do
-          stub_ci_builds_disabled
-          stub_ci_commit_yaml_file(nil)
+      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
+        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
+
+        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'
+          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/login_spec.rb b/spec/features/login_spec.rb
index 2451e56fe7ce6338b3863e06dee98c2351bec2bc..dac9205449a468f0ef47cfd4bc170e9b4446cab8 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -112,10 +112,10 @@ feature 'Login', feature: true do
       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')
+          expect(page).to have_content('You must enable Two-factor Authentication for your account before')
         end
 
-        it 'two-factor configuration is skippable' do
+        it 'disallows skipping two-factor configuration' do
           expect(current_path).to eq new_profile_two_factor_auth_path
 
           click_link 'Configure it later'
@@ -128,10 +128,10 @@ feature 'Login', feature: true 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.')
+          expect(page).to have_content('You must enable Two-factor Authentication for your account.')
         end
 
-        it 'two-factor configuration is not skippable' do
+        it 'disallows skipping two-factor configuration' do
           expect(current_path).to eq new_profile_two_factor_auth_path
           expect(page).not_to have_link('Configure it later')
         end
@@ -146,7 +146,7 @@ feature 'Login', feature: true 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.')
+        expect(page).to have_content('You must enable Two-factor Authentication for your account.')
       end
     end
   end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index f0fc6916c4d2cdb6039a06ae98ca4f958b9c1e61..1a360cd1ebc821a14b5e562c61bf58dc25bbbff6 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -167,7 +167,7 @@ describe 'Comments', feature: true do
         end
 
         it 'should be removed when canceled' do
-          page.within(".diff-file form[rel$='#{line_code}']") do
+          page.within(".diff-file form[id$='#{line_code}']") do
             find('.js-close-discussion-note-form').trigger('click')
           end
 
diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a8b7907d4ba4a27631f54e7e979d17327a3476f6
--- /dev/null
+++ b/spec/fixtures/parallel_diff_result.yml
@@ -0,0 +1,324 @@
+---
+- :left:
+    :type: match
+    :number: 6
+    :text: "@@ -6,12 +6,18 @@ module Popen"
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+  :right:
+    :type: match
+    :number: 6
+    :text: "@@ -6,12 +6,18 @@ module Popen"
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+- :left:
+    :type:
+    :number: 6
+    :text: |2
+       <span id="LC6" class="line"></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+  :right:
+    :type:
+    :number: 6
+    :text: |2
+       <span id="LC6" class="line"></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+- :left:
+    :type:
+    :number: 7
+    :text: |2
+       <span id="LC7" class="line">  <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
+  :right:
+    :type:
+    :number: 7
+    :text: |2
+       <span id="LC7" class="line">  <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
+- :left:
+    :type:
+    :number: 8
+    :text: |2
+       <span id="LC8" class="line">    <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
+  :right:
+    :type:
+    :number: 8
+    :text: |2
+       <span id="LC8" class="line">    <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
+- :left:
+    :type: old
+    :number: 9
+    :text: |
+      -<span id="LC9" class="line">      <span class="k">raise</span> <span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9
+  :right:
+    :type: new
+    :number: 9
+    :text: |
+      +<span id="LC9" class="line">      <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9
+- :left:
+    :type:
+    :number: 10
+    :text: |2
+       <span id="LC10" class="line">    <span class="k">end</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
+  :right:
+    :type:
+    :number: 10
+    :text: |2
+       <span id="LC10" class="line">    <span class="k">end</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
+- :left:
+    :type:
+    :number: 11
+    :text: |2
+       <span id="LC11" class="line"></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
+  :right:
+    :type:
+    :number: 11
+    :text: |2
+       <span id="LC11" class="line"></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
+- :left:
+    :type:
+    :number: 12
+    :text: |2
+       <span id="LC12" class="line">    <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
+  :right:
+    :type:
+    :number: 12
+    :text: |2
+       <span id="LC12" class="line">    <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
+- :left:
+    :type: old
+    :number: 13
+    :text: |
+      -<span id="LC13" class="line">    <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&quot;PWD&quot;</span> <span class="o">=&gt;</span> <span class="n">path</span> <span class="p">}</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13
+  :right:
+    :type: old
+    :number:
+    :text: ''
+    :line_code:
+- :left:
+    :type: old
+    :number: 14
+    :text: |
+      -<span id="LC14" class="line">    <span class="n">options</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">chdir: </span><span class="n">path</span> <span class="p">}</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13
+  :right:
+    :type: new
+    :number: 13
+    :text: |
+      +<span id="LC13" class="line"></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13
+- :left:
+    :type:
+    :number:
+    :text: ''
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14
+  :right:
+    :type: new
+    :number: 14
+    :text: |
+      +<span id="LC14" class="line">    <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14
+- :left:
+    :type:
+    :number:
+    :text: ''
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
+  :right:
+    :type: new
+    :number: 15
+    :text: |
+      +<span id="LC15" class="line">      <span class="s2">&quot;PWD&quot;</span> <span class="o">=&gt;</span> <span class="n">path</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
+- :left:
+    :type:
+    :number:
+    :text: ''
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16
+  :right:
+    :type: new
+    :number: 16
+    :text: |
+      +<span id="LC16" class="line">    <span class="p">}</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16
+- :left:
+    :type:
+    :number:
+    :text: ''
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17
+  :right:
+    :type: new
+    :number: 17
+    :text: |
+      +<span id="LC17" class="line"></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17
+- :left:
+    :type:
+    :number:
+    :text: ''
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18
+  :right:
+    :type: new
+    :number: 18
+    :text: |
+      +<span id="LC18" class="line">    <span class="n">options</span> <span class="o">=</span> <span class="p">{</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18
+- :left:
+    :type:
+    :number:
+    :text: ''
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19
+  :right:
+    :type: new
+    :number: 19
+    :text: |
+      +<span id="LC19" class="line">      <span class="ss">chdir: </span><span class="n">path</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19
+- :left:
+    :type:
+    :number:
+    :text: ''
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20
+  :right:
+    :type: new
+    :number: 20
+    :text: |
+      +<span id="LC20" class="line">    <span class="p">}</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20
+- :left:
+    :type:
+    :number: 15
+    :text: |2
+       <span id="LC21" class="line"></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21
+  :right:
+    :type:
+    :number: 21
+    :text: |2
+       <span id="LC21" class="line"></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21
+- :left:
+    :type:
+    :number: 16
+    :text: |2
+       <span id="LC22" class="line">    <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22
+  :right:
+    :type:
+    :number: 22
+    :text: |2
+       <span id="LC22" class="line">    <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22
+- :left:
+    :type:
+    :number: 17
+    :text: |2
+       <span id="LC23" class="line">      <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23
+  :right:
+    :type:
+    :number: 23
+    :text: |2
+       <span id="LC23" class="line">      <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23
+- :left:
+    :type: match
+    :number: 19
+    :text: "@@ -19,6 +25,7 @@ module Popen"
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
+  :right:
+    :type: match
+    :number: 25
+    :text: "@@ -19,6 +25,7 @@ module Popen"
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
+- :left:
+    :type:
+    :number: 19
+    :text: |2
+       <span id="LC25" class="line"></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
+  :right:
+    :type:
+    :number: 25
+    :text: |2
+       <span id="LC25" class="line"></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
+- :left:
+    :type:
+    :number: 20
+    :text: |2
+       <span id="LC26" class="line">    <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
+  :right:
+    :type:
+    :number: 26
+    :text: |2
+       <span id="LC26" class="line">    <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
+- :left:
+    :type:
+    :number: 21
+    :text: |2
+       <span id="LC27" class="line">    <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
+  :right:
+    :type:
+    :number: 27
+    :text: |2
+       <span id="LC27" class="line">    <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
+- :left:
+    :type:
+    :number:
+    :text: ''
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
+  :right:
+    :type: new
+    :number: 28
+    :text: |
+      +<span id="LC28" class="line"></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
+- :left:
+    :type:
+    :number: 22
+    :text: |2
+       <span id="LC29" class="line">    <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
+  :right:
+    :type:
+    :number: 29
+    :text: |2
+       <span id="LC29" class="line">    <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
+- :left:
+    :type:
+    :number: 23
+    :text: |2
+       <span id="LC30" class="line">      <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30
+  :right:
+    :type:
+    :number: 30
+    :text: |2
+       <span id="LC30" class="line">      <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30
+- :left:
+    :type:
+    :number: 24
+    :text: |2
+       <span id="LC31" class="line">      <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31
+  :right:
+    :type:
+    :number: 31
+    :text: |2
+       <span id="LC31" class="line">      <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span>
+    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index b8bba36439aecd580b94471d884e7b5e412094bd..87849230dbe97b883591d92e7330ed486a82d239 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -1,22 +1,22 @@
 require 'spec_helper'
 
 describe BlobHelper do
-  describe 'highlight' do
-    let(:blob_name) { 'test.lisp' }
-    let(:no_context_content) { ":type \"assem\"))" }
-    let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" }
-    let(:split_content) { blob_content.split("\n") }
-    let(:multiline_content) do
-      %q(
-      def test(input):
-        """This is line 1 of a multi-line comment.
-        This is line 2.
-        """
-      )
-    end
+  let(:blob_name) { 'test.lisp' }
+  let(:no_context_content) { ":type \"assem\"))" }
+  let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" }
+  let(:split_content) { blob_content.split("\n") }
+  let(:multiline_content) do
+    %q(
+    def test(input):
+      """This is line 1 of a multi-line comment.
+      This is line 2.
+      """
+    )
+  end
 
+  describe '#highlight' do
     it 'should return plaintext for unknown lexer context' do
-      result = highlight(blob_name, no_context_content, nowrap: true, continue: false)
+      result = helper.highlight(blob_name, no_context_content, nowrap: true)
       expect(result).to eq('<span id="LC1" class="line">:type &quot;assem&quot;))</span>')
     end
 
@@ -24,28 +24,17 @@ describe BlobHelper do
       expected = %Q[<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>
 <span id="LC2" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>]
 
-      expect(highlight(blob_name, blob_content, nowrap: true, continue: false)).to eq(expected)
-    end
-
-    it 'should highlight continued blocks' do
-      # Both lines have LC1 as ID since formatter doesn't support continue at the moment
-      expected = [
-        '<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>',
-        '<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>'
-      ]
-
-      result = split_content.map{ |content| highlight(blob_name, content, nowrap: true, continue: true) }
-      expect(result).to eq(expected)
+      expect(helper.highlight(blob_name, blob_content, nowrap: true)).to eq(expected)
     end
 
     it 'should highlight multi-line comments' do
-      result = highlight(blob_name, multiline_content, nowrap: true, continue: false)
+      result = helper.highlight(blob_name, multiline_content, nowrap: true)
       html = Nokogiri::HTML(result)
       lines = html.search('.s')
       expect(lines.count).to eq(3)
       expect(lines[0].text).to eq('"""This is line 1 of a multi-line comment.')
-      expect(lines[1].text).to eq('        This is line 2.')
-      expect(lines[2].text).to eq('        """')
+      expect(lines[1].text).to eq('      This is line 2.')
+      expect(lines[2].text).to eq('      """')
     end
 
     context 'diff highlighting' do
@@ -59,9 +48,23 @@ describe BlobHelper do
       end
 
       it 'should highlight each line properly' do
-        result = highlight(blob_name, blob_content, nowrap: true, continue: false)
+        result = helper.highlight(blob_name, blob_content, nowrap: true)
         expect(result).to eq(expected)
       end
     end
   end
+
+  describe "#highlighter" do
+    it 'should highlight continued blocks' do
+      # Both lines have LC1 as ID since formatter doesn't support continue at the moment
+      expected = [
+        '<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>',
+        '<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>'
+      ]
+
+      highlighter = helper.highlighter(blob_name, blob_content, nowrap: true)
+      result = split_content.map{ |content| highlighter.highlight(content) }
+      expect(result).to eq(expected)
+    end
+  end
 end
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 7c96a74e5816c0329a4094f82cd63ee95f40f3fb..14986a74c2e71316fc1299ec2e29975ae2f18a8c 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -4,10 +4,12 @@ describe DiffHelper do
   include RepoHelpers
 
   let(:project) { create(:project) }
+  let(:repository) { project.repository }
   let(:commit) { project.commit(sample_commit.id) }
   let(:diffs) { commit.diffs }
   let(:diff) { diffs.first }
-  let(:diff_file) { Gitlab::Diff::File.new(diff) }
+  let(:diff_refs) { [commit.parent, commit] }
+  let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) }
 
   describe 'diff_hard_limit_enabled?' do
     it 'should return true if param is provided' do
@@ -44,55 +46,41 @@ describe DiffHelper do
 
   describe 'safe_diff_files' do
     it 'should return all files from a commit that is smaller than safe limits' do
-      expect(safe_diff_files(diffs).length).to eq(2)
+      expect(safe_diff_files(diffs, diff_refs).length).to eq(2)
     end
 
     it 'should return only the first file if the diff line count in the 2nd file takes the total beyond safe limits' do
       allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines
-      expect(safe_diff_files(diffs).length).to eq(1)
+      expect(safe_diff_files(diffs, diff_refs).length).to eq(1)
     end
 
     it 'should return all files from a commit that is beyond safe limit for numbers of lines if force diff is true' do
       allow(controller).to receive(:params) { { force_show_diff: true } }
       allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines
-      expect(safe_diff_files(diffs).length).to eq(2)
+      expect(safe_diff_files(diffs, diff_refs).length).to eq(2)
     end
 
     it 'should return only the first file if the diff line count in the 2nd file takes the total beyond hard limits' do
       allow(controller).to receive(:params) { { force_show_diff: true } }
       allow(diffs[1].diff).to receive(:lines).and_return([""] * 49999) #simulate 49999 lines
-      expect(safe_diff_files(diffs).length).to eq(1)
+      expect(safe_diff_files(diffs, diff_refs).length).to eq(1)
     end
 
     it 'should return only a safe number of file diffs if a commit touches more files than the safe limits' do
       large_diffs = diffs * 100 #simulate 200 diffs
-      expect(safe_diff_files(large_diffs).length).to eq(100)
+      expect(safe_diff_files(large_diffs, diff_refs).length).to eq(100)
     end
 
     it 'should return all file diffs if a commit touches more files than the safe limits but force diff is true' do
       allow(controller).to receive(:params) { { force_show_diff: true } }
       large_diffs = diffs * 100 #simulate 200 diffs
-      expect(safe_diff_files(large_diffs).length).to eq(200)
+      expect(safe_diff_files(large_diffs, diff_refs).length).to eq(200)
     end
 
     it 'should return a limited file diffs if a commit touches more files than the hard limits and force diff is true' do
       allow(controller).to receive(:params) { { force_show_diff: true } }
       very_large_diffs = diffs * 1000 #simulate 2000 diffs
-      expect(safe_diff_files(very_large_diffs).length).to eq(1000)
-    end
-  end
-
-  describe 'parallel_diff' do
-    it 'should return an array of arrays containing the parsed diff' do
-      expect(parallel_diff(diff_file, 0)).
-        to match_array(parallel_diff_result_array)
-    end
-  end
-
-  describe 'generate_line_code' do
-    it 'should generate correct line code' do
-      expect(generate_line_code(diff_file.file_path, diff_file.diff_lines.first)).
-        to eq('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6')
+      expect(safe_diff_files(very_large_diffs, diff_refs).length).to eq(1000)
     end
   end
 
@@ -116,8 +104,7 @@ describe DiffHelper do
     end
   end
 
-  describe 'diff_line_content' do
-
+  describe '#diff_line_content' do
     it 'should return non breaking space when line is empty' do
       expect(diff_line_content(nil)).to eq(' &nbsp;')
     end
@@ -126,39 +113,21 @@ describe DiffHelper do
       expect(diff_line_content(diff_file.diff_lines.first.text)).
         to eq('@@ -6,12 +6,18 @@ module Popen')
       expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match')
-      expect(diff_line_content(diff_file.diff_lines.first.new_pos)).to eq(6)
+      expect(diff_file.diff_lines.first.new_pos).to eq(6)
     end
   end
 
-  def parallel_diff_result_array
-    [
-      ["match", 6, "@@ -6,12 +6,18 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6", "match", 6, "@@ -6,12 +6,18 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6"],
-      [nil, 6, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6", nil, 6, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6"], [nil, 7, "   def popen(cmd, path=nil)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7", nil, 7, "   def popen(cmd, path=nil)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"],
-      [nil, 8, "     unless cmd.is_a?(Array)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8", nil, 8, "     unless cmd.is_a?(Array)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"],
-      ["old", 9, "-      raise <span class='idiff'></span>&quot;System commands must be given as an array of strings&quot;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9", "new", 9, "+      raise <span class='idiff'>RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"],
-      [nil, 10, "     end", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10", nil, 10, "     end", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"],
-      [nil, 11, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11", nil, 11, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11"],
-      [nil, 12, "     path ||= Dir.pwd", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12", nil, 12, "     path ||= Dir.pwd", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12"],
-      ["old", 13, "-    vars = { &quot;PWD&quot; =&gt; path }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13", "old", nil, "&nbsp;", nil],
-      ["old", 14, "-    options = { chdir: path }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13", "new", 13, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13"],
-      [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14", "new", 14, "+    vars = {", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14"],
-      [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15", "new", 15, "+      &quot;PWD&quot; =&gt; path", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"],
-      [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16", "new", 16, "+    }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16"],
-      [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17", "new", 17, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17"],
-      [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18", "new", 18, "+    options = {", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18"],
-      [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19", "new", 19, "+      chdir: path", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19"],
-      [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20", "new", 20, "+    }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20"],
-      [nil, 15, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21", nil, 21, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21"],
-      [nil, 16, "     unless File.directory?(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22", nil, 22, "     unless File.directory?(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22"],
-      [nil, 17, "       FileUtils.mkdir_p(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23", nil, 23, "       FileUtils.mkdir_p(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23"],
-      ["match", 19, "@@ -19,6 +25,7 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25", "match", 25, "@@ -19,6 +25,7 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25"],
-      [nil, 19, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25", nil, 25, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25"],
-      [nil, 20, "     @cmd_output = &quot;&quot;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26", nil, 26, "     @cmd_output = &quot;&quot;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26"],
-      [nil, 21, "     @cmd_status = 0", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27", nil, 27, "     @cmd_status = 0", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27"],
-      [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28", "new", 28, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28"],
-      [nil, 22, "     Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29", nil, 29, "     Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29"],
-      [nil, 23, "       @cmd_output &lt;&lt; stdout.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30", nil, 30, "       @cmd_output &lt;&lt; stdout.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30"],
-      [nil, 24, "       @cmd_output &lt;&lt; stderr.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31", nil, 31, "       @cmd_output &lt;&lt; stderr.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31"]
-    ]
+  describe "#mark_inline_diffs" do
+    let(:old_line) { %{abc 'def'} }
+    let(:new_line) { %{abc "def"} }
+
+    it "returns strings with marked inline diffs" do
+      marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
+
+      expect(marked_old_line).to eq("abc <span class='idiff left right'>&#39;def&#39;</span>")
+      expect(marked_old_line).to be_html_safe
+      expect(marked_new_line).to eq("abc <span class='idiff left right'>&quot;def&quot;</span>")
+      expect(marked_new_line).to be_html_safe
+    end
   end
 end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 0c8d06b7059bed24926fad705e09c5d8a3299049..0b9176357bc235d8967a48a2c28328b43b87023b 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -66,5 +66,10 @@ describe LabelsHelper do
     it 'uses dark text on light backgrounds' do
       expect(text_color_for_bg('#EEEEEE')).to eq('#333333')
     end
+
+    it 'supports RGB triplets' do
+      expect(text_color_for_bg('#FFF')).to eq '#333333'
+      expect(text_color_for_bg('#000')).to eq '#FFFFFF'
+    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
index 473534ba68a66d26a5e0afd5e22ad8ad169f3271..63a32d9d455ea9dd96ba05d88800cbce20f0f7b5 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -21,7 +21,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true 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|
+    [7, 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]}")
 
@@ -35,7 +35,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
       doc = reference_filter("See #{commit.id}")
       expect(doc.text).to eq "See #{commit.short_id}"
 
-      doc = reference_filter("See #{commit.id[0...6]}")
+      doc = reference_filter("See #{commit.id[0...7]}")
       expect(doc.text).to eq "See #{commit.short_id}"
     end
 
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 760d60a41908cedc8d72a04bef89e944034f0414..9c63d227044c79ebb0d732edb8c0008552264bee 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -75,6 +75,11 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
       expect(filter(act).to_html).to eq exp
     end
 
+    it 'allows `abbr` elements' do
+      exp = act = %q{<abbr title="HyperText Markup Language">HTML</abbr>}
+      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>}
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index d15100fc6d8c075de90677c6129d4bb0d24c8fcc..f3394910c5b34d63e0a2fd6dd4b552826dbeac65 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -336,7 +336,7 @@ module Ci
     describe "Caches" do
       it "returns cache when defined globally" do
         config = YAML.dump({
-                             cache: { paths: ["logs/", "binaries/"], untracked: true },
+                             cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
                              rspec: {
                                script: "rspec"
                              }
@@ -348,13 +348,14 @@ module Ci
         expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
           paths: ["logs/", "binaries/"],
           untracked: true,
+          key: 'key',
         )
       end
 
       it "returns cache when defined in a job" do
         config = YAML.dump({
                              rspec: {
-                               cache: { paths: ["logs/", "binaries/"], untracked: true },
+                               cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
                                script: "rspec"
                              }
                            })
@@ -365,15 +366,16 @@ module Ci
         expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
           paths: ["logs/", "binaries/"],
           untracked: true,
+          key: 'key',
         )
       end
 
       it "overwrite cache when defined for a job and globally" do
         config = YAML.dump({
-                             cache: { paths: ["logs/", "binaries/"], untracked: true },
+                             cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' },
                              rspec: {
                                script: "rspec",
-                               cache: { paths: ["test/"], untracked: false },
+                               cache: { paths: ["test/"], untracked: false, key: 'local' },
                              }
                            })
 
@@ -383,6 +385,7 @@ module Ci
         expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
           paths: ["test/"],
           untracked: false,
+          key: 'local',
         )
       end
     end
@@ -615,6 +618,20 @@ module Ci
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:paths parameter should be an array of strings")
       end
 
+      it "returns errors if cache:key is not a string" do
+        config = YAML.dump({ cache: { key: 1 }, rspec: { script: "test" } })
+        expect do
+          GitlabCiYamlProcessor.new(config)
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:key parameter should be a string")
+      end
+
+      it "returns errors if job cache:key is not an a string" do
+        config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { key: 1 } } })
+        expect do
+          GitlabCiYamlProcessor.new(config)
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:key parameter should be a string")
+      end
+
       it "returns errors if job cache:untracked is not an array of strings" do
         config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } })
         expect do
diff --git a/spec/lib/gitlab/blame_spec.rb b/spec/lib/gitlab/blame_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..89245761b6f8d37f29ad1fe41e707b5964c3c890
--- /dev/null
+++ b/spec/lib/gitlab/blame_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Gitlab::Blame, lib: true do
+  let(:project) { create(:project) }
+  let(:path) { 'files/ruby/popen.rb' }
+  let(:commit) { project.commit('master') }
+  let(:blob) { project.repository.blob_at(commit.id, path) }
+
+  describe "#groups" do
+    let(:subject) { described_class.new(blob, commit).groups(highlight: false) }
+
+    it 'groups lines properly' do
+      expect(subject.count).to eq(18)
+      expect(subject[0][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
+      expect(subject[0][:lines]).to eq(["require 'fileutils'", "require 'open3'", ""])
+
+      expect(subject[1][:commit].sha).to eq('874797c3a73b60d2187ed6e2fcabd289ff75171e')
+      expect(subject[1][:lines]).to eq(["module Popen", "  extend self"])
+
+      expect(subject[-1][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
+      expect(subject[-1][:lines]).to eq(["  end", "end"])
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
index 41257103eadbae0377580ab449a1547b18923c52..acca0b08bab3497622a089a26cd9ee614f59a682 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
@@ -4,13 +4,13 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
   let(:entries) do
     { 'path/' => {},
       'path/dir_1/' => {},
-      'path/dir_1/file_1' => {},
-      'path/dir_1/file_b' => {},
+      'path/dir_1/file_1' => { size: 10 },
+      'path/dir_1/file_b' => { size: 10 },
       'path/dir_1/subdir/' => {},
-      'path/dir_1/subdir/subfile' => {},
+      'path/dir_1/subdir/subfile' => { size: 10 },
       'path/second_dir' => {},
-      'path/second_dir/dir_3/file_2' => {},
-      'path/second_dir/dir_3/file_3'=> {},
+      'path/second_dir/dir_3/file_2' => { size: 10 },
+      'path/second_dir/dir_3/file_3'=> { size: 10 },
       'another_directory/'=> {},
       'another_file' => {},
       '/file/with/absolute_path' => {} }
@@ -112,6 +112,11 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
         subject { |example| path(example).empty? }
         it { is_expected.to be false }
       end
+
+      describe '#total_size' do
+        subject { |example| path(example).total_size }
+        it { is_expected.to eq(30) }
+      end
     end
   end
 
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
index 828eedfa7b03c923c8faafd2656c4818424d2568..eea01f91879ec97d2ded8e56b17119ff4b74e6c5 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -1,8 +1,8 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Build::Artifacts::Metadata do
-  def metadata(path = '')
-    described_class.new(metadata_file_path, path)
+  def metadata(path = '', **opts)
+    described_class.new(metadata_file_path, path, **opts)
   end
 
   let(:metadata_file_path) do
@@ -51,6 +51,19 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do
       end
     end
 
+    describe '#find_entries! recursively for other_artifacts_0.1.2/' do
+      subject { metadata('other_artifacts_0.1.2/', recursive: true).find_entries! }
+
+      it 'matches correct paths' do
+        expect(subject.keys).
+          to contain_exactly 'other_artifacts_0.1.2/',
+                             'other_artifacts_0.1.2/doc_sample.txt',
+                             'other_artifacts_0.1.2/another-subdirectory/',
+                             'other_artifacts_0.1.2/another-subdirectory/empty_directory/',
+                             'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif'
+      end
+    end
+
     describe '#to_entry' do
       subject { metadata('').to_entry }
       it { is_expected.to be_an_instance_of(Gitlab::Ci::Build::Artifacts::Metadata::Entry) }
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 99288da1e43f32479bfc6bc7dad7e28ea4a2681c..04cf11fc6f17525039eaa017a8bbf071f7f99d91 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -135,6 +135,17 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
         message = "resolve #{reference}"
         expect(subject.closed_by_message(message)).to eq([issue])
       end
+
+      context 'with an external issue tracker reference' do
+        it 'extracts the referenced issue' do
+          jira_project = create(:jira_project, name: 'JIRA_EXT1')
+          jira_issue = ExternalIssue.new("#{jira_project.name}-1", project: jira_project)
+          closing_issue_extractor = described_class.new jira_project
+          message = "Resolve #{jira_issue.to_reference}"
+
+          expect(closing_issue_extractor.closed_by_message(message)).to eq([jira_issue])
+        end
+      end
     end
 
     context "with a cross-project reference" do
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index c7cdf8691d61b37217bb47dd61439aa47dcb101c..0d9694f2c1304d44023ac55ee33bc3e11d79282e 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Diff::File, lib: true do
   let(:project) { create(:project) }
   let(:commit) { project.commit(sample_commit.id) }
   let(:diff) { commit.diffs.first }
-  let(:diff_file) { Gitlab::Diff::File.new(diff) }
+  let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) }
 
   describe :diff_lines do
     let(:diff_lines) { diff_file.diff_lines }
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d19bf4ac84b4e83d9676d6575dd8bb4ec0d27b33
--- /dev/null
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::Highlight, lib: true do
+  include RepoHelpers
+
+  let(:project) { create(:project) }
+  let(:commit) { project.commit(sample_commit.id) }
+  let(:diff) { commit.diffs.first }
+  let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) }
+
+  describe '#highlight' do
+    context "with a diff file" do
+      let(:subject) { Gitlab::Diff::Highlight.new(diff_file).highlight }
+
+      it 'should return Gitlab::Diff::Line elements' do
+        expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
+      end
+
+      it 'should not modify "match" lines' do
+        expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
+        expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
+      end
+
+      it 'highlights and marks unchanged lines' do
+        code = %Q{ <span id="LC7" class="line">  <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>\n}
+
+        expect(subject[2].text).to eq(code)
+      end
+
+      it 'highlights and marks removed lines' do
+        code = %Q{-<span id="LC9" class="line">      <span class="k">raise</span> <span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
+
+        expect(subject[4].text).to eq(code)
+      end
+
+      it 'highlights and marks added lines' do
+        code = %Q{+<span id="LC9" class="line">      <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
+
+        expect(subject[5].text).to eq(code)
+      end
+    end
+
+    context "with diff lines" do
+      let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines).highlight }
+
+      it 'should return Gitlab::Diff::Line elements' do
+        expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
+      end
+
+      it 'should not modify "match" lines' do
+        expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
+        expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
+      end
+
+      it 'marks unchanged lines' do
+        code = %Q{   def popen(cmd, path=nil)}
+
+        expect(subject[2].text).to eq(code)
+        expect(subject[2].text).not_to be_html_safe
+      end
+
+      it 'marks removed lines' do
+        code = %Q{-      raise "System commands must be given as an array of strings"}
+
+        expect(subject[4].text).to eq(code)
+        expect(subject[4].text).not_to be_html_safe
+      end
+
+      it 'marks added lines' do
+        code = %Q{+      raise <span class='idiff left right'>RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;}
+
+        expect(subject[5].text).to eq(code)
+        expect(subject[5].text).to be_html_safe
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ea5c31011f0c01c29fabba12291a0526fd5400d8
--- /dev/null
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::InlineDiffMarker, lib: true do
+  describe '#inline_diffs' do
+
+    context "when the rich text is html safe" do
+      let(:raw)  { "abc 'def'" }
+      let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>}.html_safe }
+      let(:inline_diffs) { [2..5] }
+      let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) }
+
+      it 'marks the inline diffs' do
+        expect(subject).to eq(%{<span class="abc">ab<span class='idiff left'>c</span></span><span class="space"><span class='idiff'> </span></span><span class="def"><span class='idiff right'>&#39;d</span>ef&#39;</span>})
+        expect(subject).to be_html_safe
+      end
+    end
+
+    context "when the text text is not html safe" do
+      let(:raw)  { "abc 'def'" }
+      let(:inline_diffs) { [2..5] }
+      let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw).mark(inline_diffs) }
+
+      it 'marks the inline diffs' do
+        expect(subject).to eq(%{ab<span class='idiff left right'>c &#39;d</span>ef&#39;})
+        expect(subject).to be_html_safe
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..95a993d26cf70be4c9be3f01a688cb7278b75e5a
--- /dev/null
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::InlineDiff, lib: true do
+  describe '.for_lines' do
+    let(:diff) do
+      <<eos
+ class Test
+-  def initialize(test = true)
++  def initialize(test = false)
+     @test = test
+   end
+ end
+eos
+    end
+
+    let(:subject) { described_class.for_lines(diff.lines) }
+
+    it 'finds all inline diffs' do
+      expect(subject[0]).to be_nil
+      expect(subject[1]).to eq([25..27])
+      expect(subject[2]).to eq([25..28])
+      expect(subject[3]).to be_nil
+      expect(subject[4]).to be_nil
+      expect(subject[5]).to be_nil
+    end
+  end
+
+  describe "#inline_diffs" do
+    let(:old_line) { "XXX def initialize(test = true)" }
+    let(:new_line) { "YYY def initialize(test = false)" }
+    let(:subject) { described_class.new(old_line, new_line, offset: 3).inline_diffs }
+
+    it "finds the inline diff" do
+      old_diffs, new_diffs = subject
+
+      expect(old_diffs).to eq([26..28])
+      expect(new_diffs).to eq([26..29])
+    end
+  end
+end
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1c5bbc47120477528e573b0d3530bafed7866bb4
--- /dev/null
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::ParallelDiff, lib: true do
+  include RepoHelpers
+
+  let(:project) { create(:project) }
+  let(:repository) { project.repository }
+  let(:commit) { project.commit(sample_commit.id) }
+  let(:diffs) { commit.diffs }
+  let(:diff) { diffs.first }
+  let(:diff_refs) { [commit.parent, commit] }
+  let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) }
+  subject { described_class.new(diff_file) }
+
+  let(:parallel_diff_result_array) { YAML.load_file("#{Rails.root}/spec/fixtures/parallel_diff_result.yml") }
+
+  describe '#parallelize' do
+    it 'should return an array of arrays containing the parsed diff' do
+      expect(subject.parallelize).to match_array(parallel_diff_result_array)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index ba577bd28e5fa4bb1d0381376bc0a46a55341b3e..fe0dea77909ae6c3ed97c956e7010905fdfd2ceb 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -86,7 +86,7 @@ eos
         it { expect(line.type).to eq(nil) }
         it { expect(line.old_pos).to eq(24) }
         it { expect(line.new_pos).to eq(31) }
-        it { expect(line.text).to eq('       @cmd_output &lt;&lt; stderr.read') }
+        it { expect(line.text).to eq('       @cmd_output << stderr.read') }
       end
     end
   end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index 9aefec77f6d53882b25467ad7ce3ae95c03d3ca5..6cebcb5009ad99728a9eafa199402e215eaf4977 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -2,8 +2,11 @@ require 'spec_helper'
 
 describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
   let(:project) { create(:project) }
-  let(:source_branch) { OpenStruct.new(ref: 'feature') }
-  let(:target_branch) { OpenStruct.new(ref: 'master') }
+  let(:repository) { OpenStruct.new(id: 1, fork: false) }
+  let(:source_repo) { repository }
+  let(:source_branch) { OpenStruct.new(ref: 'feature', repo: source_repo) }
+  let(:target_repo) { repository }
+  let(:target_branch) { OpenStruct.new(ref: 'master', repo: target_repo) }
   let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') }
   let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
   let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
@@ -125,10 +128,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
   end
 
   describe '#cross_project?' do
-    context 'when source repo is not a fork' do
-      let(:local_repo) { OpenStruct.new(fork: false) }
-      let(:source_branch) { OpenStruct.new(ref: 'feature', repo: local_repo) }
-      let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch)) }
+    context 'when source, and target repositories are the same' do
+      let(:raw_data) { OpenStruct.new(base_data) }
 
       it 'returns false' do
         expect(pull_request.cross_project?).to eq false
@@ -136,9 +137,17 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
     end
 
     context 'when source repo is a fork' do
-      let(:forked_repo) { OpenStruct.new(fork: true) }
-      let(:source_branch) { OpenStruct.new(ref: 'feature', repo: forked_repo) }
-      let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch)) }
+      let(:source_repo) { OpenStruct.new(id: 2, fork: true) }
+      let(:raw_data) { OpenStruct.new(base_data) }
+
+      it 'returns true' do
+        expect(pull_request.cross_project?).to eq true
+      end
+    end
+
+    context 'when target repo is a fork' do
+      let(:target_repo) { OpenStruct.new(id: 2, fork: true) }
+      let(:raw_data) { OpenStruct.new(base_data) }
 
       it 'returns true' do
         expect(pull_request.cross_project?).to eq true
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1620eb6c60adb507920d56cd7cbf47b8828961a5
--- /dev/null
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::Highlight, lib: true do
+  include RepoHelpers
+
+  let(:project) { create(:project) }
+  let(:commit) { project.commit(sample_commit.id) }
+
+  describe '.highlight_lines' do
+    let(:lines) do
+      Gitlab::Highlight.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb')
+    end
+
+    it 'should properly highlight all the lines' do
+      expect(lines[4]).to eq(%Q{<span id="LC5" class="line">  <span class="kp">extend</span> <span class="nb">self</span></span>\n})
+      expect(lines[21]).to eq(%Q{<span id="LC22" class="line">    <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n})
+      expect(lines[26]).to eq(%Q{<span id="LC27" class="line">    <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n})
+    end
+  end
+
+end
diff --git a/spec/lib/gitlab/inline_diff_spec.rb b/spec/lib/gitlab/inline_diff_spec.rb
deleted file mode 100644
index c690c195112fd8e7fa173c060b3858c24134a0fa..0000000000000000000000000000000000000000
--- a/spec/lib/gitlab/inline_diff_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::InlineDiff, lib: true do
-  describe '#processing' do
-    let(:diff) do
-      <<eos
---- a/test.rb
-+++ b/test.rb
-@@ -1,6 +1,6 @@
- class Test
-   def cleanup_string(input)
-     return nil if input.nil?
--    input.sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip
-+    input.to_s.sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip
-   end
- end
-eos
-    end
-
-    let(:expected) do
-      ["--- a/test.rb\n",
-       "+++ b/test.rb\n",
-       "@@ -1,6 +1,6 @@\n",
-       " class Test\n",
-       "   def cleanup_string(input)\n",
-       "     return nil if input.nil?\n",
-       "-    input.#!idiff-start!##!idiff-finish!#sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip\n",
-       "+    input.#!idiff-start!#to_s.#!idiff-finish!#sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip\n",
-       "   end\n",
-       " end\n"]
-    end
-
-    let(:subject) { Gitlab::InlineDiff.processing(diff.lines) }
-
-    it 'should retain backslashes' do
-      expect(subject).to eq(expected)
-    end
-  end
-end
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index 2a37cd40dde178182ac0c2d7637d62446c803d77..ad4290c43bbc725aaffd40e756f144f156f6bb32 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -66,6 +66,16 @@ describe Gitlab::Metrics::Instrumentation do
 
         @dummy.foo
       end
+
+      it 'generates a method with the correct arity when using methods without arguments' do
+        dummy = Class.new do
+          def self.test; end
+        end
+
+        described_class.instrument_method(dummy, :test)
+
+        expect(dummy.method(:test).arity).to eq(0)
+      end
     end
 
     describe 'with metrics disabled' do
diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb
index 6cbdae737f4ef679c50bb60527bda6ef12834af5..691f36e6cb74eab260512ba516fe7077a36a1a24 100644
--- a/spec/lib/gitlab/note_data_builder_spec.rb
+++ b/spec/lib/gitlab/note_data_builder_spec.rb
@@ -37,7 +37,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
 
     it 'returns the note and issue-specific data' do
       expect(data).to have_key(:issue)
-      expect(data[:issue]).to eq(issue.hook_attrs)
+      expect(data[:issue].except('updated_at')).to eq(issue.hook_attrs.except('updated_at'))
+      expect(data[:issue]['updated_at']).to be > issue.hook_attrs['updated_at']
     end
   end
 
@@ -47,7 +48,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
 
     it 'returns the note and merge request data' do
       expect(data).to have_key(:merge_request)
-      expect(data[:merge_request]).to eq(merge_request.hook_attrs)
+      expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at'))
+      expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at']
     end
   end
 
@@ -57,7 +59,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
 
     it 'returns the note and merge request diff data' do
       expect(data).to have_key(:merge_request)
-      expect(data[:merge_request]).to eq(merge_request.hook_attrs)
+      expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at'))
+      expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at']
     end
   end
 
@@ -67,7 +70,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
 
     it 'returns the note and project snippet data' do
       expect(data).to have_key(:snippet)
-      expect(data[:snippet]).to eq(snippet.hook_attrs)
+      expect(data[:snippet].except('updated_at')).to eq(snippet.hook_attrs.except('updated_at'))
+      expect(data[:snippet]['updated_at']).to be > snippet.hook_attrs['updated_at']
     end
   end
 end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 91b250265e65f177389c8e97eded6c210ee78538..f4c588827575ef3f69b4cd9b1b68688d011ae6ac 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -41,6 +41,8 @@
 #  recaptcha_site_key                :string
 #  recaptcha_private_key             :string
 #  metrics_port                      :integer          default(8089)
+#  sentry_enabled                    :boolean          default(FALSE)
+#  sentry_dsn                        :string
 #
 
 require 'spec_helper'
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index d12b9e65c8239a4ba910a425ba1ccc902f502d24..606340d87e42966181ecee1b3dc47b4e7ccaf993 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Ci::Build, models: true do
-  let(:project) { FactoryGirl.create :empty_project }
+  let(:project) { FactoryGirl.create :project }
   let(:commit) { FactoryGirl.create :ci_commit, project: project }
   let(:build) { FactoryGirl.create :ci_build, commit: commit }
 
@@ -362,12 +362,12 @@ describe Ci::Build, models: true do
     subject { build.artifacts_browse_url }
 
     it "should be nil if artifacts browser is unsupported" do
-      allow(build).to receive(:artifacts_browser_supported?).and_return(false)
+      allow(build).to receive(:artifacts_metadata?).and_return(false)
       is_expected.to be_nil
     end
 
     it 'should not be nil if artifacts browser is supported' do
-      allow(build).to receive(:artifacts_browser_supported?).and_return(true)
+      allow(build).to receive(:artifacts_metadata?).and_return(true)
       is_expected.to_not be_nil
     end
   end
@@ -391,8 +391,8 @@ describe Ci::Build, models: true do
   end
 
 
-  describe :artifacts_browser_supported? do
-    subject { build.artifacts_browser_supported? }
+  describe :artifacts_metadata? do
+    subject { build.artifacts_metadata? }
     context 'artifacts metadata does not exist' do
       it { is_expected.to be_falsy }
     end
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index 25b3f4e50dafe5057370f6b0df2312076e1a4d71..92fdc5cd65da4008f691869bdbd8ea87649dc077 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -37,7 +37,7 @@ describe CaseSensitivity, models: true do
             with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar').
             and_return(criteria)
 
-          expect(model.iwhere(:'foo.bar' => 'bar')).to eq(criteria)
+          expect(model.iwhere('foo.bar'.to_sym => 'bar')).to eq(criteria)
         end
       end
 
@@ -87,8 +87,8 @@ describe CaseSensitivity, models: true do
             with(%q{LOWER("foo"."baz") = LOWER(:value)}, value: 'baz').
             and_return(final)
 
-          got = model.iwhere(:'foo.bar' => 'bar',
-                             :'foo.baz' => 'baz')
+          got = model.iwhere('foo.bar'.to_sym => 'bar',
+                             'foo.baz'.to_sym => 'baz')
 
           expect(got).to eq(final)
         end
@@ -127,7 +127,7 @@ describe CaseSensitivity, models: true do
             with(%q{`foo`.`bar` = :value}, value: 'bar').
             and_return(criteria)
 
-          expect(model.iwhere(:'foo.bar' => 'bar')).
+          expect(model.iwhere('foo.bar'.to_sym => 'bar')).
             to eq(criteria)
         end
       end
@@ -178,8 +178,8 @@ describe CaseSensitivity, models: true do
             with(%q{`foo`.`baz` = :value}, value: 'baz').
             and_return(final)
 
-          got = model.iwhere(:'foo.bar' => 'bar',
-                             :'foo.baz' => 'baz')
+          got = model.iwhere('foo.bar'.to_sym => 'bar',
+                             'foo.baz'.to_sym => 'baz')
 
           expect(got).to eq(final)
         end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 071582b028219395f2e9da775c591b805c3f289c..ec2a923f91bb4a4c4af968f034afda442e865316 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -65,27 +65,6 @@ describe Event, models: true do
     it { expect(@event.author).to eq(@user) }
   end
 
-  describe '.latest_update_time' do
-    describe 'when events are present' do
-      let(:time) { Time.utc(2015, 1, 1) }
-
-      before do
-        create(:closed_issue_event, updated_at: time)
-        create(:closed_issue_event, updated_at: time + 5)
-      end
-
-      it 'returns the latest update time' do
-        expect(Event.latest_update_time).to eq(time + 5)
-      end
-    end
-
-    describe 'when no events exist' do
-      it 'returns nil' do
-        expect(Event.latest_update_time).to be_nil
-      end
-    end
-  end
-
   describe '.limit_recent' do
     let!(:event1) { create(:closed_issue_event) }
     let!(:event2) { create(:closed_issue_event) }
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index 6ec6b9037a4c295661e00e22766bfce53759d577..9b144dd1ecccdb81e38d0ed6ea64f2a7710ad5b6 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -10,6 +10,21 @@ describe ExternalIssue, models: true do
     it { is_expected.to include_module(Referable) }
   end
 
+  describe '.reference_pattern' do
+    it 'allows underscores in the project name' do
+      expect(ExternalIssue.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+    end
+
+    it 'allows numbers in the project name' do
+      expect(ExternalIssue.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234'
+    end
+
+    it 'requires the project name to begin with A-Z' do
+      expect(ExternalIssue.reference_pattern.match('3EXT_EXT-1234')).to eq nil
+      expect(ExternalIssue.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+    end
+  end
+
   describe '#to_reference' do
     it 'returns a String reference to the object' do
       expect(issue.to_reference).to eq issue.id
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 138b87a9a061c642f6ffa2adef4e136bbd04a874..fd1513cab1b04071017dae48922182db63b4736c 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -36,7 +36,7 @@ describe SystemHook, models: true do
     it "project_destroy hook" do
       user = create(:user)
       project = create(:empty_project, namespace: user.namespace)
-      Projects::DestroyService.new(project, user, {}).execute
+      Projects::DestroyService.new(project, user, {}).pending_delete!
       expect(WebMock).to have_requested(:post, @system_hook.url).with(
         body: /project_destroy/,
         headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
@@ -65,7 +65,7 @@ describe SystemHook, models: true do
       project = create(:project)
       project.team << [user, :master]
       expect(WebMock).to have_requested(:post, @system_hook.url).with(
-        body: /user_add_to_team/, 
+        body: /user_add_to_team/,
         headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
       ).once
     end
@@ -76,7 +76,7 @@ describe SystemHook, models: true do
       project.team << [user, :master]
       project.project_members.destroy_all
       expect(WebMock).to have_requested(:post, @system_hook.url).with(
-        body: /user_remove_from_team/, 
+        body: /user_remove_from_team/,
         headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
       ).once
     end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index afbf62035acef575c2b44e9d59e2dc4e5ce3da6d..c484ae8fc8cebcd1a445c91931396e343338e124 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -219,4 +219,24 @@ describe Repository, models: true do
       end
     end
   end
+
+  describe '#has_visible_content?' do
+    subject { repository.has_visible_content? }
+
+    describe 'when there are no branches' do
+      before do
+        allow(repository.raw_repository).to receive(:branch_count).and_return(0)
+      end
+
+      it { is_expected.to eq(false) }
+    end
+
+    describe 'when there are branches' do
+      before do
+        allow(repository.raw_repository).to receive(:branch_count).and_return(3)
+      end
+
+      it { is_expected.to eq(true) }
+    end
+  end
 end
diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0737999e1254e17562764a6631ec4a9ae6a5214c
--- /dev/null
+++ b/spec/models/tree_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+
+describe Tree, models: true do
+  let(:repository) { create(:project).repository }
+  let(:sha) { repository.root_ref }
+
+  subject { described_class.new(repository, '54fcc214') }
+
+  describe '#readme' do
+    class FakeBlob
+      attr_reader :name
+
+      def initialize(name)
+        @name = name
+      end
+
+      def readme?
+        name =~ /^readme/i
+      end
+    end
+
+    it 'returns nil when repository does not contains a README file' do
+      files = [FakeBlob.new('file'), FakeBlob.new('license'), FakeBlob.new('copying')]
+      expect(subject).to receive(:blobs).and_return(files)
+
+      expect(subject.readme).to eq nil
+    end
+
+    it 'returns nil when repository does not contains a previewable README file' do
+      files = [FakeBlob.new('file'), FakeBlob.new('README.pages'), FakeBlob.new('README.png')]
+      expect(subject).to receive(:blobs).and_return(files)
+
+      expect(subject.readme).to eq nil
+    end
+
+    it 'returns README when repository contains a previewable README file' do
+      files = [FakeBlob.new('README.png'), FakeBlob.new('README'), FakeBlob.new('file')]
+      expect(subject).to receive(:blobs).and_return(files)
+
+      expect(subject.readme.name).to eq 'README'
+    end
+
+    it 'returns first previewable README when repository contains more than one' do
+      files = [FakeBlob.new('file'), FakeBlob.new('README.md'), FakeBlob.new('README.asciidoc')]
+      expect(subject).to receive(:blobs).and_return(files)
+
+      expect(subject.readme.name).to eq 'README.md'
+    end
+
+    it 'returns first plain text README when repository contains more than one' do
+      files = [FakeBlob.new('file'), FakeBlob.new('README'), FakeBlob.new('README.txt')]
+      expect(subject).to receive(:blobs).and_return(files)
+
+      expect(subject.readme.name).to eq 'README'
+    end
+
+    it 'prioritizes previewable README file over one in plain text' do
+      files = [FakeBlob.new('file'), FakeBlob.new('README'), FakeBlob.new('README.md')]
+      expect(subject).to receive(:blobs).and_return(files)
+
+      expect(subject.readme.name).to eq 'README.md'
+    end
+  end
+end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index c1b03838aa9da41ac1cc12c6c011dfc160b40582..ddc49495eda48591d15140256026ca66cdf4c48a 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -189,6 +189,38 @@ describe WikiPage, models: true do
     end
   end
 
+  describe '#historical?' do
+    before do
+      create_page('Update', 'content')
+      @page = wiki.find_page('Update')
+      3.times { |i| @page.update("content #{i}") }
+    end
+
+    after do
+      destroy_page('Update')
+    end
+
+    it 'returns true when requesting an old version' do
+      old_version = @page.versions.last.to_s
+      old_page = wiki.find_page('Update', old_version)
+
+      expect(old_page.historical?).to eq true
+    end
+
+    it 'returns false when requesting latest version' do
+      latest_version = @page.versions.first.to_s
+      latest_page = wiki.find_page('Update', latest_version)
+
+      expect(latest_page.historical?).to eq false
+    end
+
+    it 'returns false when version is nil' do
+      latest_page = wiki.find_page('Update', nil)
+
+      expect(latest_page.historical?).to eq false
+    end
+  end
+
   private
 
   def remove_temp_repo(path)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index e194eb93cf48757cd7b21ee53080be87ab74a8c3..d7bfa17b0b1c7e428df55d071ddc330b468b240d 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -109,9 +109,9 @@ describe API::API, api: true  do
     end
   end
 
-  describe "GET /projects/:id/merge_request/:merge_request_id" do
+  describe "GET /projects/:id/merge_requests/:merge_request_id" do
     it "should return merge_request" do
-      get api("/projects/#{project.id}/merge_request/#{merge_request.id}", user)
+      get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
       expect(response.status).to eq(200)
       expect(json_response['title']).to eq(merge_request.title)
       expect(json_response['iid']).to eq(merge_request.iid)
@@ -126,14 +126,14 @@ describe API::API, api: true  do
     end
 
     it "should return a 404 error if merge_request_id not found" do
-      get api("/projects/#{project.id}/merge_request/999", user)
+      get api("/projects/#{project.id}/merge_requests/999", user)
       expect(response.status).to eq(404)
     end
   end
 
-  describe 'GET /projects/:id/merge_request/:merge_request_id/commits' do
+  describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do
     context 'valid merge request' do
-      before { get api("/projects/#{project.id}/merge_request/#{merge_request.id}/commits", user) }
+      before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user) }
       let(:commit) { merge_request.commits.first }
 
       it { expect(response.status).to eq 200 }
@@ -143,20 +143,20 @@ describe API::API, api: true  do
     end
 
     it 'returns a 404 when merge_request_id not found' do
-      get api("/projects/#{project.id}/merge_request/999/commits", user)
+      get api("/projects/#{project.id}/merge_requests/999/commits", user)
       expect(response.status).to eq(404)
     end
   end
 
-  describe 'GET /projects/:id/merge_request/:merge_request_id/changes' do
+  describe 'GET /projects/:id/merge_requests/: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)
+      get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
       expect(response.status).to eq 200
       expect(json_response['changes'].size).to eq(merge_request.diffs.size)
     end
 
     it 'returns a 404 when merge_request_id not found' do
-      get api("/projects/#{project.id}/merge_request/999/changes", user)
+      get api("/projects/#{project.id}/merge_requests/999/changes", user)
       expect(response.status).to eq(404)
     end
   end
@@ -311,19 +311,19 @@ describe API::API, api: true  do
     end
   end
 
-  describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
+  describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do
     it "should return merge_request" do
-      put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "close"
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
       expect(response.status).to eq(200)
       expect(json_response['state']).to eq('closed')
     end
   end
 
-  describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do
+  describe "PUT /projects/:id/merge_requests/: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)
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
 
       expect(response.status).to eq(200)
     end
@@ -332,7 +332,7 @@ describe API::API, api: true  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)
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
 
       expect(response.status).to eq(406)
       expect(json_response['message']).to eq('Branch cannot be merged')
@@ -340,14 +340,14 @@ describe API::API, api: true  do
 
     it "should return 405 if merge_request is not open" do
       merge_request.close
-      put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
       expect(response.status).to eq(405)
       expect(json_response['message']).to eq('405 Method Not Allowed')
     end
 
     it "should return 405 if merge_request is a work in progress" do
       merge_request.update_attribute(:title, "WIP: #{merge_request.title}")
-      put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
       expect(response.status).to eq(405)
       expect(json_response['message']).to eq('405 Method Not Allowed')
     end
@@ -355,7 +355,7 @@ describe API::API, api: true  do
     it "should return 401 if user has no permissions to merge" do
       user2 = create(:user)
       project.team << [user2, :reporter]
-      put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user2)
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2)
       expect(response.status).to eq(401)
       expect(json_response['message']).to eq('401 Unauthorized')
     end
@@ -364,7 +364,7 @@ describe API::API, api: true  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
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true
 
       expect(response.status).to eq(200)
       expect(json_response['title']).to eq('Test')
@@ -372,33 +372,33 @@ describe API::API, api: true  do
     end
   end
 
-  describe "PUT /projects/:id/merge_request/:merge_request_id" do
+  describe "PUT /projects/:id/merge_requests/:merge_request_id" do
     it "should return merge_request" do
-      put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title"
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title"
       expect(response.status).to eq(200)
       expect(json_response['title']).to eq('New title')
     end
 
     it "should return merge_request" do
-      put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), description: "New description"
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description"
       expect(response.status).to eq(200)
       expect(json_response['description']).to eq('New description')
     end
 
     it "should return 400 when source_branch is specified" do
-      put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user),
       source_branch: "master", target_branch: "master"
       expect(response.status).to eq(400)
     end
 
     it "should return merge_request with renamed target_branch" do
-      put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "wiki"
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki"
       expect(response.status).to eq(200)
       expect(json_response['target_branch']).to eq('wiki')
     end
 
     it 'should return 400 on invalid label names' do
-      put api("/projects/#{project.id}/merge_request/#{merge_request.id}",
+      put api("/projects/#{project.id}/merge_requests/#{merge_request.id}",
               user),
           title: 'new issue',
           labels: 'label, ?'
@@ -407,11 +407,11 @@ describe API::API, api: true  do
     end
   end
 
-  describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
+  describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do
     it "should return comment" do
       original_count = merge_request.notes.size
 
-      post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user), note: "My comment"
+      post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment"
       expect(response.status).to eq(201)
       expect(json_response['note']).to eq('My comment')
       expect(json_response['author']['name']).to eq(user.name)
@@ -420,20 +420,20 @@ describe API::API, api: true  do
     end
 
     it "should return 400 if note is missing" do
-      post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
+      post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
       expect(response.status).to eq(400)
     end
 
     it "should return 404 if note is attached to non existent merge request" do
-      post api("/projects/#{project.id}/merge_request/404/comments", user),
+      post api("/projects/#{project.id}/merge_requests/404/comments", user),
            note: 'My comment'
       expect(response.status).to eq(404)
     end
   end
 
-  describe "GET :id/merge_request/:merge_request_id/comments" do
+  describe "GET :id/merge_requests/:merge_request_id/comments" do
     it "should return merge_request comments ordered by created_at" do
-      get api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
+      get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
       expect(response.status).to eq(200)
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(2)
@@ -443,7 +443,7 @@ describe API::API, api: true  do
     end
 
     it "should return a 404 error if merge_request_id not found" do
-      get api("/projects/#{project.id}/merge_request/999/comments", user)
+      get api("/projects/#{project.id}/merge_requests/999/comments", user)
       expect(response.status).to eq(404)
     end
   end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index eec927102aa4bbd8dd53e8854f66d1be72a9f426..244947762dd3f136ed7650a27835da1aeec4c0eb 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -113,6 +113,21 @@ describe Ci::API::API do
         expect(json_response["depends_on_builds"].count).to eq(2)
         expect(json_response["depends_on_builds"][0]["name"]).to eq("rspec")
       end
+
+      %w(name version revision platform architecture).each do |param|
+        context "updates runner #{param}" do
+          let(:value) { "#{param}_value" }
+
+          subject { runner.read_attribute(param.to_sym) }
+
+          it do
+            post ci_api("/builds/register"), token: runner.token, info: { param => value }
+            expect(response.status).to eq(404)
+            runner.reload
+            is_expected.to eq(value)
+          end
+        end
+      end
     end
 
     describe "PUT /builds/:id" do
diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb
index 5942aa7a1b59f27cade4f9b0fe08018bd0a76ea4..db8189ffb79521076762dc157cd36f0390bfac88 100644
--- a/spec/requests/ci/api/runners_spec.rb
+++ b/spec/requests/ci/api/runners_spec.rb
@@ -51,6 +51,20 @@ describe Ci::API::API do
 
       expect(response.status).to eq(400)
     end
+
+    %w(name version revision platform architecture).each do |param|
+      context "creates runner with #{param} saved" do
+        let(:value) { "#{param}_value" }
+
+        subject { Ci::Runner.first.read_attribute(param.to_sym) }
+
+        it do
+          post ci_api("/runners/register"), token: registration_token, info: { param => value }
+          expect(response.status).to eq(201)
+          is_expected.to eq(value)
+        end
+      end
+    end
   end
 
   describe "DELETE /runners/delete" do
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 22ba25217f0612f492c46818b4589b3ba3998c3a..538f44e4f3fbeffa0f7432cb98a66b99fff5a22e 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -321,12 +321,12 @@ describe Projects::HooksController, 'routing' do
   end
 end
 
-# project_commit GET    /:project_id/commit/:id(.:format) commit#show {id: /[[:alnum:]]{6,40}/, project_id: /[^\/]+/}
+# project_commit GET    /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/}
 describe Projects::CommitController, 'routing' do
   it 'to #show' do
-    expect(get('/gitlab/gitlabhq/commit/4246fb')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb')
-    expect(get('/gitlab/gitlabhq/commit/4246fb.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'diff')
-    expect(get('/gitlab/gitlabhq/commit/4246fb.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'patch')
+    expect(get('/gitlab/gitlabhq/commit/4246fbd')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd')
+    expect(get('/gitlab/gitlabhq/commit/4246fbd.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'diff')
+    expect(get('/gitlab/gitlabhq/commit/4246fbd.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'patch')
     expect(get('/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5')
   end
 end
@@ -496,11 +496,11 @@ end
 
 describe Projects::ForksController, 'routing' do
   it 'to #new' do
-    expect(get('/gitlab/gitlabhq/fork/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
+    expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
   end
 
   it 'to #create' do
-    expect(post('/gitlab/gitlabhq/fork')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
+    expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
   end
 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
index 449cecaa7896803fc65055a491f41ff8980d61ce..de9fed2b7dd590f662177bf8a915f98a3cdee0a6 100644
--- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
@@ -6,7 +6,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
 
   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_branch: "master", target_branch: 'feature',
                            source_project: project, target_project: project, state: "opened")
   end
 
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index a797a2fe4aaee479a90f9ecaf704f0e44596ed1c..ff23f13e1cb3569a9e959670fc70e2ec5605d5ea 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -14,9 +14,7 @@ describe Notes::CreateService, services: true do
           noteable_type: 'Issue',
           noteable_id: issue.id
         }
-
-        expect(project).to receive(:execute_hooks)
-        expect(project).to receive(:execute_services)
+        
         @note = Notes::CreateService.new(project, user, opts).execute
       end
 
diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1a3f339bd641a1c0f3ffb94de1f8abce7fae2cb3
--- /dev/null
+++ b/spec/services/notes/post_process_service_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Notes::PostProcessService, services: true do
+  let(:project) { create(:empty_project) }
+  let(:issue) { create(:issue, project: project) }
+  let(:user) { create(:user) }
+
+  describe :execute do
+    before do
+      project.team << [user, :master]
+      note_opts = {
+        note: 'Awesome comment',
+        noteable_type: 'Issue',
+        noteable_id: issue.id
+      }
+
+      @note = Notes::CreateService.new(project, user, note_opts).execute
+    end
+
+    it do
+      expect(project).to receive(:execute_hooks)
+      expect(project).to receive(:execute_services)
+      Notes::PostProcessService.new(@note).execute
+    end
+  end
+end
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..04f474c736cf62aa1d1b8f6a1ea206b88af6b527
--- /dev/null
+++ b/spec/services/projects/import_service_spec.rb
@@ -0,0 +1,106 @@
+require 'spec_helper'
+
+describe Projects::ImportService, services: true do
+  let!(:project) { create(:empty_project) }
+  let(:user) { project.creator }
+
+  subject { described_class.new(project, user) }
+
+  describe '#execute' do
+    context 'with unknown url' do
+      before do
+        project.import_url = Project::UNKNOWN_IMPORT_URL
+      end
+
+      it 'succeeds if repository is created successfully' do
+        expect(project).to receive(:create_repository).and_return(true)
+
+        result = subject.execute
+
+        expect(result[:status]).to eq :success
+      end
+
+      it 'fails if repository creation fails' do
+        expect(project).to receive(:create_repository).and_return(false)
+
+        result = subject.execute
+
+        expect(result[:status]).to eq :error
+        expect(result[:message]).to eq 'The repository could not be created.'
+      end
+    end
+
+    context 'with known url' do
+      before do
+        project.import_url = 'https://github.com/vim/vim.git'
+      end
+
+      it 'succeeds if repository import is successfully' do
+        expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+
+        result = subject.execute
+
+        expect(result[:status]).to eq :success
+      end
+
+      it 'fails if repository import fails' do
+        expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_raise(Gitlab::Shell::Error.new('Failed to import the repository'))
+
+        result = subject.execute
+
+        expect(result[:status]).to eq :error
+        expect(result[:message]).to eq 'Failed to import the repository'
+      end
+    end
+
+    context 'with valid importer' do
+      before do
+        stub_github_omniauth_provider
+
+        project.import_url = 'https://github.com/vim/vim.git'
+        project.import_type = 'github'
+
+        allow(project).to receive(:import_data).and_return(double.as_null_object)
+      end
+
+      it 'succeeds if importer succeeds' do
+        expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+        expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true)
+
+        result = subject.execute
+
+        expect(result[:status]).to eq :success
+      end
+
+      it 'fails if importer fails' do
+        expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+        expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(false)
+
+        result = subject.execute
+
+        expect(result[:status]).to eq :error
+        expect(result[:message]).to eq 'The remote data could not be imported.'
+      end
+
+      it 'fails if importer raise an error' do
+        expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+        expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API'))
+
+        result = subject.execute
+
+        expect(result[:status]).to eq :error
+        expect(result[:message]).to eq 'Github: failed to connect API'
+      end
+    end
+
+    def stub_github_omniauth_provider
+      provider = OpenStruct.new(
+        name: 'github',
+        app_id: 'asd123',
+        app_secret: 'asd123'
+      )
+
+      Gitlab.config.omniauth.providers << provider
+    end
+  end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 0225a0ee53f27a87b9ad87b6a3bc4c597a3e494c..8f381f46e5774c269466e1603b02f9f63ff99ece 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -48,4 +48,10 @@ FactoryGirl::SyntaxRunner.class_eval do
   include RSpec::Mocks::ExampleMethods
 end
 
+# Work around a Rails 4.2.5.1 issue
+# See https://github.com/rspec/rspec-rails/issues/1532
+RSpec::Rails::ViewRendering::EmptyTemplatePathSetDecorator.class_eval do
+  alias_method :find_all_anywhere, :find_all
+end
+
 ActiveRecord::Migration.maintain_test_schema!
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index fed1ab6ee33f743b181ab69ab8a74056b5020978..a698f484df192813fbcc271f5b3554bc1f3654bf 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -19,3 +19,9 @@ unless ENV['CI'] || ENV['CI_SERVER']
   # Keep only the screenshots generated from the last failing test suite
   Capybara::Screenshot.prune_strategy = :keep_last_run
 end
+
+RSpec.configure do |config|
+  config.before(:suite) do
+    TestEnv.warm_asset_cache
+  end
+end
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index d6e03cbef3dbb940c8e97349c8c9bbb8aac2ba2f..ef5ea7d626e8f75f48a8bd4754caffd4964ea7f8 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -67,9 +67,9 @@ module FilterSpecHelper
     if reference =~ /\A(.+)?.\d+\z/
       # Integer-based reference with optional project prefix
       reference.gsub(/\d+\z/) { |i| i.to_i + 1 }
-    elsif reference =~ /\A(.+@)?(\h{6,40}\z)/
+    elsif reference =~ /\A(.+@)?(\h{7,40}\z)/
       # SHA-based reference with optional prefix
-      reference.gsub(/\h{6,40}\z/) { |v| v.reverse }
+      reference.gsub(/\h{7,40}\z/) { |v| v.reverse }
     else
       reference.gsub(/\w+\z/) { |v| v.reverse }
     end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 4f4743bff6d957d5232aa390962752e5996298b6..0d1bd030f3c4c3b700d2e4a60c81c9697c4a3e3a 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -146,6 +146,22 @@ module TestEnv
     FileUtils.chmod_R 0755, target_repo_path
   end
 
+  # When no cached assets exist, manually hit the root path to create them
+  #
+  # Otherwise they'd be created by the first test, often timing out and
+  # causing a transient test failure
+  def warm_asset_cache
+    return if warm_asset_cache?
+    return unless defined?(Capybara)
+
+    Capybara.current_session.driver.visit '/'
+  end
+
+  def warm_asset_cache?
+    cache = Rails.root.join(*%w(tmp cache assets test))
+    Dir.exist?(cache) && Dir.entries(cache).length > 2
+  end
+
   private
 
   def factory_repo_path
@@ -172,7 +188,6 @@ module TestEnv
     'gitlab-test-fork'
   end
 
-
   # Prevent developer git configurations from being persisted to test
   # repositories
   def git_env
diff --git a/vendor/assets/javascripts/Chart.js b/vendor/assets/javascripts/Chart.js
new file mode 100755
index 0000000000000000000000000000000000000000..c264262ba7352f75c1c2112866e98b2c1fcf9173
--- /dev/null
+++ b/vendor/assets/javascripts/Chart.js
@@ -0,0 +1,3477 @@
+/*!
+ * Chart.js
+ * http://chartjs.org/
+ * Version: 1.0.2
+ *
+ * Copyright 2015 Nick Downie
+ * Released under the MIT license
+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
+ */
+
+
+(function(){
+
+	"use strict";
+
+	//Declare root variable - window in the browser, global on the server
+	var root = this,
+		previous = root.Chart;
+
+	//Occupy the global variable of Chart, and create a simple base class
+	var Chart = function(context){
+		var chart = this;
+		this.canvas = context.canvas;
+
+		this.ctx = context;
+
+		//Variables global to the chart
+		var computeDimension = function(element,dimension)
+		{
+			if (element['offset'+dimension])
+			{
+				return element['offset'+dimension];
+			}
+			else
+			{
+				return document.defaultView.getComputedStyle(element).getPropertyValue(dimension);
+			}
+		}
+
+		var width = this.width = computeDimension(context.canvas,'Width');
+		var height = this.height = computeDimension(context.canvas,'Height');
+
+		// Firefox requires this to work correctly
+		context.canvas.width  = width;
+		context.canvas.height = height;
+
+		var width = this.width = context.canvas.width;
+		var height = this.height = context.canvas.height;
+		this.aspectRatio = this.width / this.height;
+		//High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
+		helpers.retinaScale(this);
+
+		return this;
+	};
+	//Globally expose the defaults to allow for user updating/changing
+	Chart.defaults = {
+		global: {
+			// Boolean - Whether to animate the chart
+			animation: true,
+
+			// Number - Number of animation steps
+			animationSteps: 60,
+
+			// String - Animation easing effect
+			animationEasing: "easeOutQuart",
+
+			// Boolean - If we should show the scale at all
+			showScale: true,
+
+			// Boolean - If we want to override with a hard coded scale
+			scaleOverride: false,
+
+			// ** Required if scaleOverride is true **
+			// Number - The number of steps in a hard coded scale
+			scaleSteps: null,
+			// Number - The value jump in the hard coded scale
+			scaleStepWidth: null,
+			// Number - The scale starting value
+			scaleStartValue: null,
+
+			// String - Colour of the scale line
+			scaleLineColor: "rgba(0,0,0,.1)",
+
+			// Number - Pixel width of the scale line
+			scaleLineWidth: 1,
+
+			// Boolean - Whether to show labels on the scale
+			scaleShowLabels: true,
+
+			// Interpolated JS string - can access value
+			scaleLabel: "<%=value%>",
+
+			// Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
+			scaleIntegersOnly: true,
+
+			// Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
+			scaleBeginAtZero: false,
+
+			// String - Scale label font declaration for the scale label
+			scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+			// Number - Scale label font size in pixels
+			scaleFontSize: 12,
+
+			// String - Scale label font weight style
+			scaleFontStyle: "normal",
+
+			// String - Scale label font colour
+			scaleFontColor: "#666",
+
+			// Boolean - whether or not the chart should be responsive and resize when the browser does.
+			responsive: false,
+
+			// Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
+			maintainAspectRatio: true,
+
+			// Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
+			showTooltips: true,
+
+			// Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
+			customTooltips: false,
+
+			// Array - Array of string names to attach tooltip events
+			tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
+
+			// String - Tooltip background colour
+			tooltipFillColor: "rgba(0,0,0,0.8)",
+
+			// String - Tooltip label font declaration for the scale label
+			tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+			// Number - Tooltip label font size in pixels
+			tooltipFontSize: 14,
+
+			// String - Tooltip font weight style
+			tooltipFontStyle: "normal",
+
+			// String - Tooltip label font colour
+			tooltipFontColor: "#fff",
+
+			// String - Tooltip title font declaration for the scale label
+			tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+			// Number - Tooltip title font size in pixels
+			tooltipTitleFontSize: 14,
+
+			// String - Tooltip title font weight style
+			tooltipTitleFontStyle: "bold",
+
+			// String - Tooltip title font colour
+			tooltipTitleFontColor: "#fff",
+
+			// Number - pixel width of padding around tooltip text
+			tooltipYPadding: 6,
+
+			// Number - pixel width of padding around tooltip text
+			tooltipXPadding: 6,
+
+			// Number - Size of the caret on the tooltip
+			tooltipCaretSize: 8,
+
+			// Number - Pixel radius of the tooltip border
+			tooltipCornerRadius: 6,
+
+			// Number - Pixel offset from point x to tooltip edge
+			tooltipXOffset: 10,
+
+			// String - Template string for single tooltips
+			tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
+
+			// String - Template string for single tooltips
+			multiTooltipTemplate: "<%= value %>",
+
+			// String - Colour behind the legend colour block
+			multiTooltipKeyBackground: '#fff',
+
+			// Function - Will fire on animation progression.
+			onAnimationProgress: function(){},
+
+			// Function - Will fire on animation completion.
+			onAnimationComplete: function(){}
+
+		}
+	};
+
+	//Create a dictionary of chart types, to allow for extension of existing types
+	Chart.types = {};
+
+	//Global Chart helpers object for utility methods and classes
+	var helpers = Chart.helpers = {};
+
+		//-- Basic js utility methods
+	var each = helpers.each = function(loopable,callback,self){
+			var additionalArgs = Array.prototype.slice.call(arguments, 3);
+			// Check to see if null or undefined firstly.
+			if (loopable){
+				if (loopable.length === +loopable.length){
+					var i;
+					for (i=0; i<loopable.length; i++){
+						callback.apply(self,[loopable[i], i].concat(additionalArgs));
+					}
+				}
+				else{
+					for (var item in loopable){
+						callback.apply(self,[loopable[item],item].concat(additionalArgs));
+					}
+				}
+			}
+		},
+		clone = helpers.clone = function(obj){
+			var objClone = {};
+			each(obj,function(value,key){
+				if (obj.hasOwnProperty(key)) objClone[key] = value;
+			});
+			return objClone;
+		},
+		extend = helpers.extend = function(base){
+			each(Array.prototype.slice.call(arguments,1), function(extensionObject) {
+				each(extensionObject,function(value,key){
+					if (extensionObject.hasOwnProperty(key)) base[key] = value;
+				});
+			});
+			return base;
+		},
+		merge = helpers.merge = function(base,master){
+			//Merge properties in left object over to a shallow clone of object right.
+			var args = Array.prototype.slice.call(arguments,0);
+			args.unshift({});
+			return extend.apply(null, args);
+		},
+		indexOf = helpers.indexOf = function(arrayToSearch, item){
+			if (Array.prototype.indexOf) {
+				return arrayToSearch.indexOf(item);
+			}
+			else{
+				for (var i = 0; i < arrayToSearch.length; i++) {
+					if (arrayToSearch[i] === item) return i;
+				}
+				return -1;
+			}
+		},
+		where = helpers.where = function(collection, filterCallback){
+			var filtered = [];
+
+			helpers.each(collection, function(item){
+				if (filterCallback(item)){
+					filtered.push(item);
+				}
+			});
+
+			return filtered;
+		},
+		findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex){
+			// Default to start of the array
+			if (!startIndex){
+				startIndex = -1;
+			}
+			for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
+				var currentItem = arrayToSearch[i];
+				if (filterCallback(currentItem)){
+					return currentItem;
+				}
+			}
+		},
+		findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex){
+			// Default to end of the array
+			if (!startIndex){
+				startIndex = arrayToSearch.length;
+			}
+			for (var i = startIndex - 1; i >= 0; i--) {
+				var currentItem = arrayToSearch[i];
+				if (filterCallback(currentItem)){
+					return currentItem;
+				}
+			}
+		},
+		inherits = helpers.inherits = function(extensions){
+			//Basic javascript inheritance based on the model created in Backbone.js
+			var parent = this;
+			var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };
+
+			var Surrogate = function(){ this.constructor = ChartElement;};
+			Surrogate.prototype = parent.prototype;
+			ChartElement.prototype = new Surrogate();
+
+			ChartElement.extend = inherits;
+
+			if (extensions) extend(ChartElement.prototype, extensions);
+
+			ChartElement.__super__ = parent.prototype;
+
+			return ChartElement;
+		},
+		noop = helpers.noop = function(){},
+		uid = helpers.uid = (function(){
+			var id=0;
+			return function(){
+				return "chart-" + id++;
+			};
+		})(),
+		warn = helpers.warn = function(str){
+			//Method for warning of errors
+			if (window.console && typeof window.console.warn == "function") console.warn(str);
+		},
+		amd = helpers.amd = (typeof define == 'function' && define.amd),
+		//-- Math methods
+		isNumber = helpers.isNumber = function(n){
+			return !isNaN(parseFloat(n)) && isFinite(n);
+		},
+		max = helpers.max = function(array){
+			return Math.max.apply( Math, array );
+		},
+		min = helpers.min = function(array){
+			return Math.min.apply( Math, array );
+		},
+		cap = helpers.cap = function(valueToCap,maxValue,minValue){
+			if(isNumber(maxValue)) {
+				if( valueToCap > maxValue ) {
+					return maxValue;
+				}
+			}
+			else if(isNumber(minValue)){
+				if ( valueToCap < minValue ){
+					return minValue;
+				}
+			}
+			return valueToCap;
+		},
+		getDecimalPlaces = helpers.getDecimalPlaces = function(num){
+			if (num%1!==0 && isNumber(num)){
+				return num.toString().split(".")[1].length;
+			}
+			else {
+				return 0;
+			}
+		},
+		toRadians = helpers.radians = function(degrees){
+			return degrees * (Math.PI/180);
+		},
+		// Gets the angle from vertical upright to the point about a centre.
+		getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
+			var distanceFromXCenter = anglePoint.x - centrePoint.x,
+				distanceFromYCenter = anglePoint.y - centrePoint.y,
+				radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
+
+
+			var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
+
+			//If the segment is in the top left quadrant, we need to add another rotation to the angle
+			if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
+				angle += Math.PI*2;
+			}
+
+			return {
+				angle: angle,
+				distance: radialDistanceFromCenter
+			};
+		},
+		aliasPixel = helpers.aliasPixel = function(pixelWidth){
+			return (pixelWidth % 2 === 0) ? 0 : 0.5;
+		},
+		splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
+			//Props to Rob Spencer at scaled innovation for his post on splining between points
+			//http://scaledinnovation.com/analytics/splines/aboutSplines.html
+			var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
+				d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
+				fa=t*d01/(d01+d12),// scaling factor for triangle Ta
+				fb=t*d12/(d01+d12);
+			return {
+				inner : {
+					x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
+					y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
+				},
+				outer : {
+					x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
+					y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
+				}
+			};
+		},
+		calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
+			return Math.floor(Math.log(val) / Math.LN10);
+		},
+		calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){
+
+			//Set a minimum step of two - a point at the top of the graph, and a point at the base
+			var minSteps = 2,
+				maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
+				skipFitting = (minSteps >= maxSteps);
+
+			var maxValue = max(valuesArray),
+				minValue = min(valuesArray);
+
+			// We need some degree of seperation here to calculate the scales if all the values are the same
+			// Adding/minusing 0.5 will give us a range of 1.
+			if (maxValue === minValue){
+				maxValue += 0.5;
+				// So we don't end up with a graph with a negative start value if we've said always start from zero
+				if (minValue >= 0.5 && !startFromZero){
+					minValue -= 0.5;
+				}
+				else{
+					// Make up a whole number above the values
+					maxValue += 0.5;
+				}
+			}
+
+			var	valueRange = Math.abs(maxValue - minValue),
+				rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
+				graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
+				graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
+				graphRange = graphMax - graphMin,
+				stepValue = Math.pow(10, rangeOrderOfMagnitude),
+				numberOfSteps = Math.round(graphRange / stepValue);
+
+			//If we have more space on the graph we'll use it to give more definition to the data
+			while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
+				if(numberOfSteps > maxSteps){
+					stepValue *=2;
+					numberOfSteps = Math.round(graphRange/stepValue);
+					// Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
+					if (numberOfSteps % 1 !== 0){
+						skipFitting = true;
+					}
+				}
+				//We can fit in double the amount of scale points on the scale
+				else{
+					//If user has declared ints only, and the step value isn't a decimal
+					if (integersOnly && rangeOrderOfMagnitude >= 0){
+						//If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
+						if(stepValue/2 % 1 === 0){
+							stepValue /=2;
+							numberOfSteps = Math.round(graphRange/stepValue);
+						}
+						//If it would make it a float break out of the loop
+						else{
+							break;
+						}
+					}
+					//If the scale doesn't have to be an int, make the scale more granular anyway.
+					else{
+						stepValue /=2;
+						numberOfSteps = Math.round(graphRange/stepValue);
+					}
+
+				}
+			}
+
+			if (skipFitting){
+				numberOfSteps = minSteps;
+				stepValue = graphRange / numberOfSteps;
+			}
+
+			return {
+				steps : numberOfSteps,
+				stepValue : stepValue,
+				min : graphMin,
+				max	: graphMin + (numberOfSteps * stepValue)
+			};
+
+		},
+		/* jshint ignore:start */
+		// Blows up jshint errors based on the new Function constructor
+		//Templating methods
+		//Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
+		template = helpers.template = function(templateString, valuesObject){
+
+			// If templateString is function rather than string-template - call the function for valuesObject
+
+			if(templateString instanceof Function){
+			 	return templateString(valuesObject);
+		 	}
+
+			var cache = {};
+			function tmpl(str, data){
+				// Figure out if we're getting a template, or if we need to
+				// load the template - and be sure to cache the result.
+				var fn = !/\W/.test(str) ?
+				cache[str] = cache[str] :
+
+				// Generate a reusable function that will serve as a template
+				// generator (and which will be cached).
+				new Function("obj",
+					"var p=[],print=function(){p.push.apply(p,arguments);};" +
+
+					// Introduce the data as local variables using with(){}
+					"with(obj){p.push('" +
+
+					// Convert the template into pure JavaScript
+					str
+						.replace(/[\r\t\n]/g, " ")
+						.split("<%").join("\t")
+						.replace(/((^|%>)[^\t]*)'/g, "$1\r")
+						.replace(/\t=(.*?)%>/g, "',$1,'")
+						.split("\t").join("');")
+						.split("%>").join("p.push('")
+						.split("\r").join("\\'") +
+					"');}return p.join('');"
+				);
+
+				// Provide some basic currying to the user
+				return data ? fn( data ) : fn;
+			}
+			return tmpl(templateString,valuesObject);
+		},
+		/* jshint ignore:end */
+		generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
+			var labelsArray = new Array(numberOfSteps);
+			if (labelTemplateString){
+				each(labelsArray,function(val,index){
+					labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
+				});
+			}
+			return labelsArray;
+		},
+		//--Animation methods
+		//Easing functions adapted from Robert Penner's easing equations
+		//http://www.robertpenner.com/easing/
+		easingEffects = helpers.easingEffects = {
+			linear: function (t) {
+				return t;
+			},
+			easeInQuad: function (t) {
+				return t * t;
+			},
+			easeOutQuad: function (t) {
+				return -1 * t * (t - 2);
+			},
+			easeInOutQuad: function (t) {
+				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
+				return -1 / 2 * ((--t) * (t - 2) - 1);
+			},
+			easeInCubic: function (t) {
+				return t * t * t;
+			},
+			easeOutCubic: function (t) {
+				return 1 * ((t = t / 1 - 1) * t * t + 1);
+			},
+			easeInOutCubic: function (t) {
+				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
+				return 1 / 2 * ((t -= 2) * t * t + 2);
+			},
+			easeInQuart: function (t) {
+				return t * t * t * t;
+			},
+			easeOutQuart: function (t) {
+				return -1 * ((t = t / 1 - 1) * t * t * t - 1);
+			},
+			easeInOutQuart: function (t) {
+				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
+				return -1 / 2 * ((t -= 2) * t * t * t - 2);
+			},
+			easeInQuint: function (t) {
+				return 1 * (t /= 1) * t * t * t * t;
+			},
+			easeOutQuint: function (t) {
+				return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
+			},
+			easeInOutQuint: function (t) {
+				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
+				return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
+			},
+			easeInSine: function (t) {
+				return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
+			},
+			easeOutSine: function (t) {
+				return 1 * Math.sin(t / 1 * (Math.PI / 2));
+			},
+			easeInOutSine: function (t) {
+				return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
+			},
+			easeInExpo: function (t) {
+				return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
+			},
+			easeOutExpo: function (t) {
+				return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
+			},
+			easeInOutExpo: function (t) {
+				if (t === 0) return 0;
+				if (t === 1) return 1;
+				if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
+				return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
+			},
+			easeInCirc: function (t) {
+				if (t >= 1) return t;
+				return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
+			},
+			easeOutCirc: function (t) {
+				return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
+			},
+			easeInOutCirc: function (t) {
+				if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
+				return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
+			},
+			easeInElastic: function (t) {
+				var s = 1.70158;
+				var p = 0;
+				var a = 1;
+				if (t === 0) return 0;
+				if ((t /= 1) == 1) return 1;
+				if (!p) p = 1 * 0.3;
+				if (a < Math.abs(1)) {
+					a = 1;
+					s = p / 4;
+				} else s = p / (2 * Math.PI) * Math.asin(1 / a);
+				return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+			},
+			easeOutElastic: function (t) {
+				var s = 1.70158;
+				var p = 0;
+				var a = 1;
+				if (t === 0) return 0;
+				if ((t /= 1) == 1) return 1;
+				if (!p) p = 1 * 0.3;
+				if (a < Math.abs(1)) {
+					a = 1;
+					s = p / 4;
+				} else s = p / (2 * Math.PI) * Math.asin(1 / a);
+				return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
+			},
+			easeInOutElastic: function (t) {
+				var s = 1.70158;
+				var p = 0;
+				var a = 1;
+				if (t === 0) return 0;
+				if ((t /= 1 / 2) == 2) return 1;
+				if (!p) p = 1 * (0.3 * 1.5);
+				if (a < Math.abs(1)) {
+					a = 1;
+					s = p / 4;
+				} else s = p / (2 * Math.PI) * Math.asin(1 / a);
+				if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+				return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
+			},
+			easeInBack: function (t) {
+				var s = 1.70158;
+				return 1 * (t /= 1) * t * ((s + 1) * t - s);
+			},
+			easeOutBack: function (t) {
+				var s = 1.70158;
+				return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
+			},
+			easeInOutBack: function (t) {
+				var s = 1.70158;
+				if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
+				return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
+			},
+			easeInBounce: function (t) {
+				return 1 - easingEffects.easeOutBounce(1 - t);
+			},
+			easeOutBounce: function (t) {
+				if ((t /= 1) < (1 / 2.75)) {
+					return 1 * (7.5625 * t * t);
+				} else if (t < (2 / 2.75)) {
+					return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
+				} else if (t < (2.5 / 2.75)) {
+					return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
+				} else {
+					return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
+				}
+			},
+			easeInOutBounce: function (t) {
+				if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
+				return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
+			}
+		},
+		//Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
+		requestAnimFrame = helpers.requestAnimFrame = (function(){
+			return window.requestAnimationFrame ||
+				window.webkitRequestAnimationFrame ||
+				window.mozRequestAnimationFrame ||
+				window.oRequestAnimationFrame ||
+				window.msRequestAnimationFrame ||
+				function(callback) {
+					return window.setTimeout(callback, 1000 / 60);
+				};
+		})(),
+		cancelAnimFrame = helpers.cancelAnimFrame = (function(){
+			return window.cancelAnimationFrame ||
+				window.webkitCancelAnimationFrame ||
+				window.mozCancelAnimationFrame ||
+				window.oCancelAnimationFrame ||
+				window.msCancelAnimationFrame ||
+				function(callback) {
+					return window.clearTimeout(callback, 1000 / 60);
+				};
+		})(),
+		animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
+
+			var currentStep = 0,
+				easingFunction = easingEffects[easingString] || easingEffects.linear;
+
+			var animationFrame = function(){
+				currentStep++;
+				var stepDecimal = currentStep/totalSteps;
+				var easeDecimal = easingFunction(stepDecimal);
+
+				callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
+				onProgress.call(chartInstance,easeDecimal,stepDecimal);
+				if (currentStep < totalSteps){
+					chartInstance.animationFrame = requestAnimFrame(animationFrame);
+				} else{
+					onComplete.apply(chartInstance);
+				}
+			};
+			requestAnimFrame(animationFrame);
+		},
+		//-- DOM methods
+		getRelativePosition = helpers.getRelativePosition = function(evt){
+			var mouseX, mouseY;
+			var e = evt.originalEvent || evt,
+				canvas = evt.currentTarget || evt.srcElement,
+				boundingRect = canvas.getBoundingClientRect();
+
+			if (e.touches){
+				mouseX = e.touches[0].clientX - boundingRect.left;
+				mouseY = e.touches[0].clientY - boundingRect.top;
+
+			}
+			else{
+				mouseX = e.clientX - boundingRect.left;
+				mouseY = e.clientY - boundingRect.top;
+			}
+
+			return {
+				x : mouseX,
+				y : mouseY
+			};
+
+		},
+		addEvent = helpers.addEvent = function(node,eventType,method){
+			if (node.addEventListener){
+				node.addEventListener(eventType,method);
+			} else if (node.attachEvent){
+				node.attachEvent("on"+eventType, method);
+			} else {
+				node["on"+eventType] = method;
+			}
+		},
+		removeEvent = helpers.removeEvent = function(node, eventType, handler){
+			if (node.removeEventListener){
+				node.removeEventListener(eventType, handler, false);
+			} else if (node.detachEvent){
+				node.detachEvent("on"+eventType,handler);
+			} else{
+				node["on" + eventType] = noop;
+			}
+		},
+		bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
+			// Create the events object if it's not already present
+			if (!chartInstance.events) chartInstance.events = {};
+
+			each(arrayOfEvents,function(eventName){
+				chartInstance.events[eventName] = function(){
+					handler.apply(chartInstance, arguments);
+				};
+				addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
+			});
+		},
+		unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
+			each(arrayOfEvents, function(handler,eventName){
+				removeEvent(chartInstance.chart.canvas, eventName, handler);
+			});
+		},
+		getMaximumWidth = helpers.getMaximumWidth = function(domNode){
+			var container = domNode.parentNode;
+			// TODO = check cross browser stuff with this.
+			return container.clientWidth;
+		},
+		getMaximumHeight = helpers.getMaximumHeight = function(domNode){
+			var container = domNode.parentNode;
+			// TODO = check cross browser stuff with this.
+			return container.clientHeight;
+		},
+		getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
+		retinaScale = helpers.retinaScale = function(chart){
+			var ctx = chart.ctx,
+				width = chart.canvas.width,
+				height = chart.canvas.height;
+
+			if (window.devicePixelRatio) {
+				ctx.canvas.style.width = width + "px";
+				ctx.canvas.style.height = height + "px";
+				ctx.canvas.height = height * window.devicePixelRatio;
+				ctx.canvas.width = width * window.devicePixelRatio;
+				ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
+			}
+		},
+		//-- Canvas methods
+		clear = helpers.clear = function(chart){
+			chart.ctx.clearRect(0,0,chart.width,chart.height);
+		},
+		fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
+			return fontStyle + " " + pixelSize+"px " + fontFamily;
+		},
+		longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
+			ctx.font = font;
+			var longest = 0;
+			each(arrayOfStrings,function(string){
+				var textWidth = ctx.measureText(string).width;
+				longest = (textWidth > longest) ? textWidth : longest;
+			});
+			return longest;
+		},
+		drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
+			ctx.beginPath();
+			ctx.moveTo(x + radius, y);
+			ctx.lineTo(x + width - radius, y);
+			ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+			ctx.lineTo(x + width, y + height - radius);
+			ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+			ctx.lineTo(x + radius, y + height);
+			ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+			ctx.lineTo(x, y + radius);
+			ctx.quadraticCurveTo(x, y, x + radius, y);
+			ctx.closePath();
+		};
+
+
+	//Store a reference to each instance - allowing us to globally resize chart instances on window resize.
+	//Destroy method on the chart will remove the instance of the chart from this reference.
+	Chart.instances = {};
+
+	Chart.Type = function(data,options,chart){
+		this.options = options;
+		this.chart = chart;
+		this.id = uid();
+		//Add the chart instance to the global namespace
+		Chart.instances[this.id] = this;
+
+		// Initialize is always called when a chart type is created
+		// By default it is a no op, but it should be extended
+		if (options.responsive){
+			this.resize();
+		}
+		this.initialize.call(this,data);
+	};
+
+	//Core methods that'll be a part of every chart type
+	extend(Chart.Type.prototype,{
+		initialize : function(){return this;},
+		clear : function(){
+			clear(this.chart);
+			return this;
+		},
+		stop : function(){
+			// Stops any current animation loop occuring
+			cancelAnimFrame(this.animationFrame);
+			return this;
+		},
+		resize : function(callback){
+			this.stop();
+			var canvas = this.chart.canvas,
+				newWidth = getMaximumWidth(this.chart.canvas),
+				newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
+
+			canvas.width = this.chart.width = newWidth;
+			canvas.height = this.chart.height = newHeight;
+
+			retinaScale(this.chart);
+
+			if (typeof callback === "function"){
+				callback.apply(this, Array.prototype.slice.call(arguments, 1));
+			}
+			return this;
+		},
+		reflow : noop,
+		render : function(reflow){
+			if (reflow){
+				this.reflow();
+			}
+			if (this.options.animation && !reflow){
+				helpers.animationLoop(
+					this.draw,
+					this.options.animationSteps,
+					this.options.animationEasing,
+					this.options.onAnimationProgress,
+					this.options.onAnimationComplete,
+					this
+				);
+			}
+			else{
+				this.draw();
+				this.options.onAnimationComplete.call(this);
+			}
+			return this;
+		},
+		generateLegend : function(){
+			return template(this.options.legendTemplate,this);
+		},
+		destroy : function(){
+			this.clear();
+			unbindEvents(this, this.events);
+			var canvas = this.chart.canvas;
+
+			// Reset canvas height/width attributes starts a fresh with the canvas context
+			canvas.width = this.chart.width;
+			canvas.height = this.chart.height;
+
+			// < IE9 doesn't support removeProperty
+			if (canvas.style.removeProperty) {
+				canvas.style.removeProperty('width');
+				canvas.style.removeProperty('height');
+			} else {
+				canvas.style.removeAttribute('width');
+				canvas.style.removeAttribute('height');
+			}
+
+			delete Chart.instances[this.id];
+		},
+		showTooltip : function(ChartElements, forceRedraw){
+			// Only redraw the chart if we've actually changed what we're hovering on.
+			if (typeof this.activeElements === 'undefined') this.activeElements = [];
+
+			var isChanged = (function(Elements){
+				var changed = false;
+
+				if (Elements.length !== this.activeElements.length){
+					changed = true;
+					return changed;
+				}
+
+				each(Elements, function(element, index){
+					if (element !== this.activeElements[index]){
+						changed = true;
+					}
+				}, this);
+				return changed;
+			}).call(this, ChartElements);
+
+			if (!isChanged && !forceRedraw){
+				return;
+			}
+			else{
+				this.activeElements = ChartElements;
+			}
+			this.draw();
+			if(this.options.customTooltips){
+				this.options.customTooltips(false);
+			}
+			if (ChartElements.length > 0){
+				// If we have multiple datasets, show a MultiTooltip for all of the data points at that index
+				if (this.datasets && this.datasets.length > 1) {
+					var dataArray,
+						dataIndex;
+
+					for (var i = this.datasets.length - 1; i >= 0; i--) {
+						dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
+						dataIndex = indexOf(dataArray, ChartElements[0]);
+						if (dataIndex !== -1){
+							break;
+						}
+					}
+					var tooltipLabels = [],
+						tooltipColors = [],
+						medianPosition = (function(index) {
+
+							// Get all the points at that particular index
+							var Elements = [],
+								dataCollection,
+								xPositions = [],
+								yPositions = [],
+								xMax,
+								yMax,
+								xMin,
+								yMin;
+							helpers.each(this.datasets, function(dataset){
+								dataCollection = dataset.points || dataset.bars || dataset.segments;
+								if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
+									Elements.push(dataCollection[dataIndex]);
+								}
+							});
+
+							helpers.each(Elements, function(element) {
+								xPositions.push(element.x);
+								yPositions.push(element.y);
+
+
+								//Include any colour information about the element
+								tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
+								tooltipColors.push({
+									fill: element._saved.fillColor || element.fillColor,
+									stroke: element._saved.strokeColor || element.strokeColor
+								});
+
+							}, this);
+
+							yMin = min(yPositions);
+							yMax = max(yPositions);
+
+							xMin = min(xPositions);
+							xMax = max(xPositions);
+
+							return {
+								x: (xMin > this.chart.width/2) ? xMin : xMax,
+								y: (yMin + yMax)/2
+							};
+						}).call(this, dataIndex);
+
+					new Chart.MultiTooltip({
+						x: medianPosition.x,
+						y: medianPosition.y,
+						xPadding: this.options.tooltipXPadding,
+						yPadding: this.options.tooltipYPadding,
+						xOffset: this.options.tooltipXOffset,
+						fillColor: this.options.tooltipFillColor,
+						textColor: this.options.tooltipFontColor,
+						fontFamily: this.options.tooltipFontFamily,
+						fontStyle: this.options.tooltipFontStyle,
+						fontSize: this.options.tooltipFontSize,
+						titleTextColor: this.options.tooltipTitleFontColor,
+						titleFontFamily: this.options.tooltipTitleFontFamily,
+						titleFontStyle: this.options.tooltipTitleFontStyle,
+						titleFontSize: this.options.tooltipTitleFontSize,
+						cornerRadius: this.options.tooltipCornerRadius,
+						labels: tooltipLabels,
+						legendColors: tooltipColors,
+						legendColorBackground : this.options.multiTooltipKeyBackground,
+						title: ChartElements[0].label,
+						chart: this.chart,
+						ctx: this.chart.ctx,
+						custom: this.options.customTooltips
+					}).draw();
+
+				} else {
+					each(ChartElements, function(Element) {
+						var tooltipPosition = Element.tooltipPosition();
+						new Chart.Tooltip({
+							x: Math.round(tooltipPosition.x),
+							y: Math.round(tooltipPosition.y),
+							xPadding: this.options.tooltipXPadding,
+							yPadding: this.options.tooltipYPadding,
+							fillColor: this.options.tooltipFillColor,
+							textColor: this.options.tooltipFontColor,
+							fontFamily: this.options.tooltipFontFamily,
+							fontStyle: this.options.tooltipFontStyle,
+							fontSize: this.options.tooltipFontSize,
+							caretHeight: this.options.tooltipCaretSize,
+							cornerRadius: this.options.tooltipCornerRadius,
+							text: template(this.options.tooltipTemplate, Element),
+							chart: this.chart,
+							custom: this.options.customTooltips
+						}).draw();
+					}, this);
+				}
+			}
+			return this;
+		},
+		toBase64Image : function(){
+			return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
+		}
+	});
+
+	Chart.Type.extend = function(extensions){
+
+		var parent = this;
+
+		var ChartType = function(){
+			return parent.apply(this,arguments);
+		};
+
+		//Copy the prototype object of the this class
+		ChartType.prototype = clone(parent.prototype);
+		//Now overwrite some of the properties in the base class with the new extensions
+		extend(ChartType.prototype, extensions);
+
+		ChartType.extend = Chart.Type.extend;
+
+		if (extensions.name || parent.prototype.name){
+
+			var chartName = extensions.name || parent.prototype.name;
+			//Assign any potential default values of the new chart type
+
+			//If none are defined, we'll use a clone of the chart type this is being extended from.
+			//I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
+			//doesn't define some defaults of their own.
+
+			var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
+
+			Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);
+
+			Chart.types[chartName] = ChartType;
+
+			//Register this new chart type in the Chart prototype
+			Chart.prototype[chartName] = function(data,options){
+				var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
+				return new ChartType(data,config,this);
+			};
+		} else{
+			warn("Name not provided for this chart, so it hasn't been registered");
+		}
+		return parent;
+	};
+
+	Chart.Element = function(configuration){
+		extend(this,configuration);
+		this.initialize.apply(this,arguments);
+		this.save();
+	};
+	extend(Chart.Element.prototype,{
+		initialize : function(){},
+		restore : function(props){
+			if (!props){
+				extend(this,this._saved);
+			} else {
+				each(props,function(key){
+					this[key] = this._saved[key];
+				},this);
+			}
+			return this;
+		},
+		save : function(){
+			this._saved = clone(this);
+			delete this._saved._saved;
+			return this;
+		},
+		update : function(newProps){
+			each(newProps,function(value,key){
+				this._saved[key] = this[key];
+				this[key] = value;
+			},this);
+			return this;
+		},
+		transition : function(props,ease){
+			each(props,function(value,key){
+				this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
+			},this);
+			return this;
+		},
+		tooltipPosition : function(){
+			return {
+				x : this.x,
+				y : this.y
+			};
+		},
+		hasValue: function(){
+			return isNumber(this.value);
+		}
+	});
+
+	Chart.Element.extend = inherits;
+
+
+	Chart.Point = Chart.Element.extend({
+		display: true,
+		inRange: function(chartX,chartY){
+			var hitDetectionRange = this.hitDetectionRadius + this.radius;
+			return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
+		},
+		draw : function(){
+			if (this.display){
+				var ctx = this.ctx;
+				ctx.beginPath();
+
+				ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
+				ctx.closePath();
+
+				ctx.strokeStyle = this.strokeColor;
+				ctx.lineWidth = this.strokeWidth;
+
+				ctx.fillStyle = this.fillColor;
+
+				ctx.fill();
+				ctx.stroke();
+			}
+
+
+			//Quick debug for bezier curve splining
+			//Highlights control points and the line between them.
+			//Handy for dev - stripped in the min version.
+
+			// ctx.save();
+			// ctx.fillStyle = "black";
+			// ctx.strokeStyle = "black"
+			// ctx.beginPath();
+			// ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
+			// ctx.fill();
+
+			// ctx.beginPath();
+			// ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
+			// ctx.fill();
+
+			// ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
+			// ctx.lineTo(this.x, this.y);
+			// ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
+			// ctx.stroke();
+
+			// ctx.restore();
+
+
+
+		}
+	});
+
+	Chart.Arc = Chart.Element.extend({
+		inRange : function(chartX,chartY){
+
+			var pointRelativePosition = helpers.getAngleFromPoint(this, {
+				x: chartX,
+				y: chartY
+			});
+
+			//Check if within the range of the open/close angle
+			var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
+				withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
+
+			return (betweenAngles && withinRadius);
+			//Ensure within the outside of the arc centre, but inside arc outer
+		},
+		tooltipPosition : function(){
+			var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
+				rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
+			return {
+				x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
+				y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
+			};
+		},
+		draw : function(animationPercent){
+
+			var easingDecimal = animationPercent || 1;
+
+			var ctx = this.ctx;
+
+			ctx.beginPath();
+
+			ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
+
+			ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
+
+			ctx.closePath();
+			ctx.strokeStyle = this.strokeColor;
+			ctx.lineWidth = this.strokeWidth;
+
+			ctx.fillStyle = this.fillColor;
+
+			ctx.fill();
+			ctx.lineJoin = 'bevel';
+
+			if (this.showStroke){
+				ctx.stroke();
+			}
+		}
+	});
+
+	Chart.Rectangle = Chart.Element.extend({
+		draw : function(){
+			var ctx = this.ctx,
+				halfWidth = this.width/2,
+				leftX = this.x - halfWidth,
+				rightX = this.x + halfWidth,
+				top = this.base - (this.base - this.y),
+				halfStroke = this.strokeWidth / 2;
+
+			// Canvas doesn't allow us to stroke inside the width so we can
+			// adjust the sizes to fit if we're setting a stroke on the line
+			if (this.showStroke){
+				leftX += halfStroke;
+				rightX -= halfStroke;
+				top += halfStroke;
+			}
+
+			ctx.beginPath();
+
+			ctx.fillStyle = this.fillColor;
+			ctx.strokeStyle = this.strokeColor;
+			ctx.lineWidth = this.strokeWidth;
+
+			// It'd be nice to keep this class totally generic to any rectangle
+			// and simply specify which border to miss out.
+			ctx.moveTo(leftX, this.base);
+			ctx.lineTo(leftX, top);
+			ctx.lineTo(rightX, top);
+			ctx.lineTo(rightX, this.base);
+			ctx.fill();
+			if (this.showStroke){
+				ctx.stroke();
+			}
+		},
+		height : function(){
+			return this.base - this.y;
+		},
+		inRange : function(chartX,chartY){
+			return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
+		}
+	});
+
+	Chart.Tooltip = Chart.Element.extend({
+		draw : function(){
+
+			var ctx = this.chart.ctx;
+
+			ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
+
+			this.xAlign = "center";
+			this.yAlign = "above";
+
+			//Distance between the actual element.y position and the start of the tooltip caret
+			var caretPadding = this.caretPadding = 2;
+
+			var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
+				tooltipRectHeight = this.fontSize + 2*this.yPadding,
+				tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
+
+			if (this.x + tooltipWidth/2 >this.chart.width){
+				this.xAlign = "left";
+			} else if (this.x - tooltipWidth/2 < 0){
+				this.xAlign = "right";
+			}
+
+			if (this.y - tooltipHeight < 0){
+				this.yAlign = "below";
+			}
+
+
+			var tooltipX = this.x - tooltipWidth/2,
+				tooltipY = this.y - tooltipHeight;
+
+			ctx.fillStyle = this.fillColor;
+
+			// Custom Tooltips
+			if(this.custom){
+				this.custom(this);
+			}
+			else{
+				switch(this.yAlign)
+				{
+				case "above":
+					//Draw a caret above the x/y
+					ctx.beginPath();
+					ctx.moveTo(this.x,this.y - caretPadding);
+					ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
+					ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
+					ctx.closePath();
+					ctx.fill();
+					break;
+				case "below":
+					tooltipY = this.y + caretPadding + this.caretHeight;
+					//Draw a caret below the x/y
+					ctx.beginPath();
+					ctx.moveTo(this.x, this.y + caretPadding);
+					ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
+					ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
+					ctx.closePath();
+					ctx.fill();
+					break;
+				}
+
+				switch(this.xAlign)
+				{
+				case "left":
+					tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
+					break;
+				case "right":
+					tooltipX = this.x - (this.cornerRadius + this.caretHeight);
+					break;
+				}
+
+				drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
+
+				ctx.fill();
+
+				ctx.fillStyle = this.textColor;
+				ctx.textAlign = "center";
+				ctx.textBaseline = "middle";
+				ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
+			}
+		}
+	});
+
+	Chart.MultiTooltip = Chart.Element.extend({
+		initialize : function(){
+			this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
+
+			this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
+
+			this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;
+
+			this.ctx.font = this.titleFont;
+
+			var titleWidth = this.ctx.measureText(this.title).width,
+				//Label has a legend square as well so account for this.
+				labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
+				longestTextWidth = max([labelWidth,titleWidth]);
+
+			this.width = longestTextWidth + (this.xPadding*2);
+
+
+			var halfHeight = this.height/2;
+
+			//Check to ensure the height will fit on the canvas
+			if (this.y - halfHeight < 0 ){
+				this.y = halfHeight;
+			} else if (this.y + halfHeight > this.chart.height){
+				this.y = this.chart.height - halfHeight;
+			}
+
+			//Decide whether to align left or right based on position on canvas
+			if (this.x > this.chart.width/2){
+				this.x -= this.xOffset + this.width;
+			} else {
+				this.x += this.xOffset;
+			}
+
+
+		},
+		getLineHeight : function(index){
+			var baseLineHeight = this.y - (this.height/2) + this.yPadding,
+				afterTitleIndex = index-1;
+
+			//If the index is zero, we're getting the title
+			if (index === 0){
+				return baseLineHeight + this.titleFontSize/2;
+			} else{
+				return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
+			}
+
+		},
+		draw : function(){
+			// Custom Tooltips
+			if(this.custom){
+				this.custom(this);
+			}
+			else{
+				drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
+				var ctx = this.ctx;
+				ctx.fillStyle = this.fillColor;
+				ctx.fill();
+				ctx.closePath();
+
+				ctx.textAlign = "left";
+				ctx.textBaseline = "middle";
+				ctx.fillStyle = this.titleTextColor;
+				ctx.font = this.titleFont;
+
+				ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
+
+				ctx.font = this.font;
+				helpers.each(this.labels,function(label,index){
+					ctx.fillStyle = this.textColor;
+					ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
+
+					//A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
+					//ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+					//Instead we'll make a white filled block to put the legendColour palette over.
+
+					ctx.fillStyle = this.legendColorBackground;
+					ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+
+					ctx.fillStyle = this.legendColors[index].fill;
+					ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+
+
+				},this);
+			}
+		}
+	});
+
+	Chart.Scale = Chart.Element.extend({
+		initialize : function(){
+			this.fit();
+		},
+		buildYLabels : function(){
+			this.yLabels = [];
+
+			var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
+
+			for (var i=0; i<=this.steps; i++){
+				this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
+			}
+			this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
+		},
+		addXLabel : function(label){
+			this.xLabels.push(label);
+			this.valuesCount++;
+			this.fit();
+		},
+		removeXLabel : function(){
+			this.xLabels.shift();
+			this.valuesCount--;
+			this.fit();
+		},
+		// Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
+		fit: function(){
+			// First we need the width of the yLabels, assuming the xLabels aren't rotated
+
+			// To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
+			this.startPoint = (this.display) ? this.fontSize : 0;
+			this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
+
+			// Apply padding settings to the start and end point.
+			this.startPoint += this.padding;
+			this.endPoint -= this.padding;
+
+			// Cache the starting height, so can determine if we need to recalculate the scale yAxis
+			var cachedHeight = this.endPoint - this.startPoint,
+				cachedYLabelWidth;
+
+			// Build the current yLabels so we have an idea of what size they'll be to start
+			/*
+			 *	This sets what is returned from calculateScaleRange as static properties of this class:
+			 *
+				this.steps;
+				this.stepValue;
+				this.min;
+				this.max;
+			 *
+			 */
+			this.calculateYRange(cachedHeight);
+
+			// With these properties set we can now build the array of yLabels
+			// and also the width of the largest yLabel
+			this.buildYLabels();
+
+			this.calculateXLabelRotation();
+
+			while((cachedHeight > this.endPoint - this.startPoint)){
+				cachedHeight = this.endPoint - this.startPoint;
+				cachedYLabelWidth = this.yLabelWidth;
+
+				this.calculateYRange(cachedHeight);
+				this.buildYLabels();
+
+				// Only go through the xLabel loop again if the yLabel width has changed
+				if (cachedYLabelWidth < this.yLabelWidth){
+					this.calculateXLabelRotation();
+				}
+			}
+
+		},
+		calculateXLabelRotation : function(){
+			//Get the width of each grid by calculating the difference
+			//between x offsets between 0 and 1.
+
+			this.ctx.font = this.font;
+
+			var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
+				lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
+				firstRotated,
+				lastRotated;
+
+
+			this.xScalePaddingRight = lastWidth/2 + 3;
+			this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
+
+			this.xLabelRotation = 0;
+			if (this.display){
+				var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
+					cosRotation,
+					firstRotatedWidth;
+				this.xLabelWidth = originalLabelWidth;
+				//Allow 3 pixels x2 padding either side for label readability
+				var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
+
+				//Max label rotate should be 90 - also act as a loop counter
+				while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
+					cosRotation = Math.cos(toRadians(this.xLabelRotation));
+
+					firstRotated = cosRotation * firstWidth;
+					lastRotated = cosRotation * lastWidth;
+
+					// We're right aligning the text now.
+					if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
+						this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
+					}
+					this.xScalePaddingRight = this.fontSize/2;
+
+
+					this.xLabelRotation++;
+					this.xLabelWidth = cosRotation * originalLabelWidth;
+
+				}
+				if (this.xLabelRotation > 0){
+					this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
+				}
+			}
+			else{
+				this.xLabelWidth = 0;
+				this.xScalePaddingRight = this.padding;
+				this.xScalePaddingLeft = this.padding;
+			}
+
+		},
+		// Needs to be overidden in each Chart type
+		// Otherwise we need to pass all the data into the scale class
+		calculateYRange: noop,
+		drawingArea: function(){
+			return this.startPoint - this.endPoint;
+		},
+		calculateY : function(value){
+			var scalingFactor = this.drawingArea() / (this.min - this.max);
+			return this.endPoint - (scalingFactor * (value - this.min));
+		},
+		calculateX : function(index){
+			var isRotated = (this.xLabelRotation > 0),
+				// innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
+				innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
+				valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
+				valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
+
+			if (this.offsetGridLines){
+				valueOffset += (valueWidth/2);
+			}
+
+			return Math.round(valueOffset);
+		},
+		update : function(newProps){
+			helpers.extend(this, newProps);
+			this.fit();
+		},
+		draw : function(){
+			var ctx = this.ctx,
+				yLabelGap = (this.endPoint - this.startPoint) / this.steps,
+				xStart = Math.round(this.xScalePaddingLeft);
+			if (this.display){
+				ctx.fillStyle = this.textColor;
+				ctx.font = this.font;
+				each(this.yLabels,function(labelString,index){
+					var yLabelCenter = this.endPoint - (yLabelGap * index),
+						linePositionY = Math.round(yLabelCenter),
+						drawHorizontalLine = this.showHorizontalLines;
+
+					ctx.textAlign = "right";
+					ctx.textBaseline = "middle";
+					if (this.showLabels){
+						ctx.fillText(labelString,xStart - 10,yLabelCenter);
+					}
+
+					// This is X axis, so draw it
+					if (index === 0 && !drawHorizontalLine){
+						drawHorizontalLine = true;
+					}
+
+					if (drawHorizontalLine){
+						ctx.beginPath();
+					}
+
+					if (index > 0){
+						// This is a grid line in the centre, so drop that
+						ctx.lineWidth = this.gridLineWidth;
+						ctx.strokeStyle = this.gridLineColor;
+					} else {
+						// This is the first line on the scale
+						ctx.lineWidth = this.lineWidth;
+						ctx.strokeStyle = this.lineColor;
+					}
+
+					linePositionY += helpers.aliasPixel(ctx.lineWidth);
+
+					if(drawHorizontalLine){
+						ctx.moveTo(xStart, linePositionY);
+						ctx.lineTo(this.width, linePositionY);
+						ctx.stroke();
+						ctx.closePath();
+					}
+
+					ctx.lineWidth = this.lineWidth;
+					ctx.strokeStyle = this.lineColor;
+					ctx.beginPath();
+					ctx.moveTo(xStart - 5, linePositionY);
+					ctx.lineTo(xStart, linePositionY);
+					ctx.stroke();
+					ctx.closePath();
+
+				},this);
+
+				each(this.xLabels,function(label,index){
+					var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
+						// Check to see if line/bar here and decide where to place the line
+						linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
+						isRotated = (this.xLabelRotation > 0),
+						drawVerticalLine = this.showVerticalLines;
+
+					// This is Y axis, so draw it
+					if (index === 0 && !drawVerticalLine){
+						drawVerticalLine = true;
+					}
+
+					if (drawVerticalLine){
+						ctx.beginPath();
+					}
+
+					if (index > 0){
+						// This is a grid line in the centre, so drop that
+						ctx.lineWidth = this.gridLineWidth;
+						ctx.strokeStyle = this.gridLineColor;
+					} else {
+						// This is the first line on the scale
+						ctx.lineWidth = this.lineWidth;
+						ctx.strokeStyle = this.lineColor;
+					}
+
+					if (drawVerticalLine){
+						ctx.moveTo(linePos,this.endPoint);
+						ctx.lineTo(linePos,this.startPoint - 3);
+						ctx.stroke();
+						ctx.closePath();
+					}
+
+
+					ctx.lineWidth = this.lineWidth;
+					ctx.strokeStyle = this.lineColor;
+
+
+					// Small lines at the bottom of the base grid line
+					ctx.beginPath();
+					ctx.moveTo(linePos,this.endPoint);
+					ctx.lineTo(linePos,this.endPoint + 5);
+					ctx.stroke();
+					ctx.closePath();
+
+					ctx.save();
+					ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
+					ctx.rotate(toRadians(this.xLabelRotation)*-1);
+					ctx.font = this.font;
+					ctx.textAlign = (isRotated) ? "right" : "center";
+					ctx.textBaseline = (isRotated) ? "middle" : "top";
+					ctx.fillText(label, 0, 0);
+					ctx.restore();
+				},this);
+
+			}
+		}
+
+	});
+
+	Chart.RadialScale = Chart.Element.extend({
+		initialize: function(){
+			this.size = min([this.height, this.width]);
+			this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
+		},
+		calculateCenterOffset: function(value){
+			// Take into account half font size + the yPadding of the top value
+			var scalingFactor = this.drawingArea / (this.max - this.min);
+
+			return (value - this.min) * scalingFactor;
+		},
+		update : function(){
+			if (!this.lineArc){
+				this.setScaleSize();
+			} else {
+				this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
+			}
+			this.buildYLabels();
+		},
+		buildYLabels: function(){
+			this.yLabels = [];
+
+			var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
+
+			for (var i=0; i<=this.steps; i++){
+				this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
+			}
+		},
+		getCircumference : function(){
+			return ((Math.PI*2) / this.valuesCount);
+		},
+		setScaleSize: function(){
+			/*
+			 * Right, this is really confusing and there is a lot of maths going on here
+			 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
+			 *
+			 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
+			 *
+			 * Solution:
+			 *
+			 * We assume the radius of the polygon is half the size of the canvas at first
+			 * at each index we check if the text overlaps.
+			 *
+			 * Where it does, we store that angle and that index.
+			 *
+			 * After finding the largest index and angle we calculate how much we need to remove
+			 * from the shape radius to move the point inwards by that x.
+			 *
+			 * We average the left and right distances to get the maximum shape radius that can fit in the box
+			 * along with labels.
+			 *
+			 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
+			 * on each side, removing that from the size, halving it and adding the left x protrusion width.
+			 *
+			 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
+			 * and position it in the most space efficient manner
+			 *
+			 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
+			 */
+
+
+			// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
+			// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
+			var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
+				pointPosition,
+				i,
+				textWidth,
+				halfTextWidth,
+				furthestRight = this.width,
+				furthestRightIndex,
+				furthestRightAngle,
+				furthestLeft = 0,
+				furthestLeftIndex,
+				furthestLeftAngle,
+				xProtrusionLeft,
+				xProtrusionRight,
+				radiusReductionRight,
+				radiusReductionLeft,
+				maxWidthRadius;
+			this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
+			for (i=0;i<this.valuesCount;i++){
+				// 5px to space the text slightly out - similar to what we do in the draw function.
+				pointPosition = this.getPointPosition(i, largestPossibleRadius);
+				textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;
+				if (i === 0 || i === this.valuesCount/2){
+					// If we're at index zero, or exactly the middle, we're at exactly the top/bottom
+					// of the radar chart, so text will be aligned centrally, so we'll half it and compare
+					// w/left and right text sizes
+					halfTextWidth = textWidth/2;
+					if (pointPosition.x + halfTextWidth > furthestRight) {
+						furthestRight = pointPosition.x + halfTextWidth;
+						furthestRightIndex = i;
+					}
+					if (pointPosition.x - halfTextWidth < furthestLeft) {
+						furthestLeft = pointPosition.x - halfTextWidth;
+						furthestLeftIndex = i;
+					}
+				}
+				else if (i < this.valuesCount/2) {
+					// Less than half the values means we'll left align the text
+					if (pointPosition.x + textWidth > furthestRight) {
+						furthestRight = pointPosition.x + textWidth;
+						furthestRightIndex = i;
+					}
+				}
+				else if (i > this.valuesCount/2){
+					// More than half the values means we'll right align the text
+					if (pointPosition.x - textWidth < furthestLeft) {
+						furthestLeft = pointPosition.x - textWidth;
+						furthestLeftIndex = i;
+					}
+				}
+			}
+
+			xProtrusionLeft = furthestLeft;
+
+			xProtrusionRight = Math.ceil(furthestRight - this.width);
+
+			furthestRightAngle = this.getIndexAngle(furthestRightIndex);
+
+			furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
+
+			radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
+
+			radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
+
+			// Ensure we actually need to reduce the size of the chart
+			radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
+			radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
+
+			this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
+
+			//this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
+			this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
+
+		},
+		setCenterPoint: function(leftMovement, rightMovement){
+
+			var maxRight = this.width - rightMovement - this.drawingArea,
+				maxLeft = leftMovement + this.drawingArea;
+
+			this.xCenter = (maxLeft + maxRight)/2;
+			// Always vertically in the centre as the text height doesn't change
+			this.yCenter = (this.height/2);
+		},
+
+		getIndexAngle : function(index){
+			var angleMultiplier = (Math.PI * 2) / this.valuesCount;
+			// Start from the top instead of right, so remove a quarter of the circle
+
+			return index * angleMultiplier - (Math.PI/2);
+		},
+		getPointPosition : function(index, distanceFromCenter){
+			var thisAngle = this.getIndexAngle(index);
+			return {
+				x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
+				y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
+			};
+		},
+		draw: function(){
+			if (this.display){
+				var ctx = this.ctx;
+				each(this.yLabels, function(label, index){
+					// Don't draw a centre value
+					if (index > 0){
+						var yCenterOffset = index * (this.drawingArea/this.steps),
+							yHeight = this.yCenter - yCenterOffset,
+							pointPosition;
+
+						// Draw circular lines around the scale
+						if (this.lineWidth > 0){
+							ctx.strokeStyle = this.lineColor;
+							ctx.lineWidth = this.lineWidth;
+
+							if(this.lineArc){
+								ctx.beginPath();
+								ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
+								ctx.closePath();
+								ctx.stroke();
+							} else{
+								ctx.beginPath();
+								for (var i=0;i<this.valuesCount;i++)
+								{
+									pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
+									if (i === 0){
+										ctx.moveTo(pointPosition.x, pointPosition.y);
+									} else {
+										ctx.lineTo(pointPosition.x, pointPosition.y);
+									}
+								}
+								ctx.closePath();
+								ctx.stroke();
+							}
+						}
+						if(this.showLabels){
+							ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
+							if (this.showLabelBackdrop){
+								var labelWidth = ctx.measureText(label).width;
+								ctx.fillStyle = this.backdropColor;
+								ctx.fillRect(
+									this.xCenter - labelWidth/2 - this.backdropPaddingX,
+									yHeight - this.fontSize/2 - this.backdropPaddingY,
+									labelWidth + this.backdropPaddingX*2,
+									this.fontSize + this.backdropPaddingY*2
+								);
+							}
+							ctx.textAlign = 'center';
+							ctx.textBaseline = "middle";
+							ctx.fillStyle = this.fontColor;
+							ctx.fillText(label, this.xCenter, yHeight);
+						}
+					}
+				}, this);
+
+				if (!this.lineArc){
+					ctx.lineWidth = this.angleLineWidth;
+					ctx.strokeStyle = this.angleLineColor;
+					for (var i = this.valuesCount - 1; i >= 0; i--) {
+						if (this.angleLineWidth > 0){
+							var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
+							ctx.beginPath();
+							ctx.moveTo(this.xCenter, this.yCenter);
+							ctx.lineTo(outerPosition.x, outerPosition.y);
+							ctx.stroke();
+							ctx.closePath();
+						}
+						// Extra 3px out for some label spacing
+						var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
+						ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
+						ctx.fillStyle = this.pointLabelFontColor;
+
+						var labelsCount = this.labels.length,
+							halfLabelsCount = this.labels.length/2,
+							quarterLabelsCount = halfLabelsCount/2,
+							upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
+							exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
+						if (i === 0){
+							ctx.textAlign = 'center';
+						} else if(i === halfLabelsCount){
+							ctx.textAlign = 'center';
+						} else if (i < halfLabelsCount){
+							ctx.textAlign = 'left';
+						} else {
+							ctx.textAlign = 'right';
+						}
+
+						// Set the correct text baseline based on outer positioning
+						if (exactQuarter){
+							ctx.textBaseline = 'middle';
+						} else if (upperHalf){
+							ctx.textBaseline = 'bottom';
+						} else {
+							ctx.textBaseline = 'top';
+						}
+
+						ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
+					}
+				}
+			}
+		}
+	});
+
+	// Attach global event to resize each chart instance when the browser resizes
+	helpers.addEvent(window, "resize", (function(){
+		// Basic debounce of resize function so it doesn't hurt performance when resizing browser.
+		var timeout;
+		return function(){
+			clearTimeout(timeout);
+			timeout = setTimeout(function(){
+				each(Chart.instances,function(instance){
+					// If the responsive flag is set in the chart instance config
+					// Cascade the resize event down to the chart.
+					if (instance.options.responsive){
+						instance.resize(instance.render, true);
+					}
+				});
+			}, 50);
+		};
+	})());
+
+
+	if (amd) {
+		define(function(){
+			return Chart;
+		});
+	} else if (typeof module === 'object' && module.exports) {
+		module.exports = Chart;
+	}
+
+	root.Chart = Chart;
+
+	Chart.noConflict = function(){
+		root.Chart = previous;
+		return Chart;
+	};
+
+}).call(this);
+
+(function(){
+	"use strict";
+
+	var root = this,
+		Chart = root.Chart,
+		helpers = Chart.helpers;
+
+
+	var defaultConfig = {
+		//Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
+		scaleBeginAtZero : true,
+
+		//Boolean - Whether grid lines are shown across the chart
+		scaleShowGridLines : true,
+
+		//String - Colour of the grid lines
+		scaleGridLineColor : "rgba(0,0,0,.05)",
+
+		//Number - Width of the grid lines
+		scaleGridLineWidth : 1,
+
+		//Boolean - Whether to show horizontal lines (except X axis)
+		scaleShowHorizontalLines: true,
+
+		//Boolean - Whether to show vertical lines (except Y axis)
+		scaleShowVerticalLines: true,
+
+		//Boolean - If there is a stroke on each bar
+		barShowStroke : true,
+
+		//Number - Pixel width of the bar stroke
+		barStrokeWidth : 2,
+
+		//Number - Spacing between each of the X value sets
+		barValueSpacing : 5,
+
+		//Number - Spacing between data sets within X values
+		barDatasetSpacing : 1,
+
+		//String - A legend template
+		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
+
+	};
+
+
+	Chart.Type.extend({
+		name: "Bar",
+		defaults : defaultConfig,
+		initialize:  function(data){
+
+			//Expose options as a scope variable here so we can access it in the ScaleClass
+			var options = this.options;
+
+			this.ScaleClass = Chart.Scale.extend({
+				offsetGridLines : true,
+				calculateBarX : function(datasetCount, datasetIndex, barIndex){
+					//Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
+					var xWidth = this.calculateBaseWidth(),
+						xAbsolute = this.calculateX(barIndex) - (xWidth/2),
+						barWidth = this.calculateBarWidth(datasetCount);
+
+					return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;
+				},
+				calculateBaseWidth : function(){
+					return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);
+				},
+				calculateBarWidth : function(datasetCount){
+					//The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
+					var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);
+
+					return (baseWidth / datasetCount);
+				}
+			});
+
+			this.datasets = [];
+
+			//Set up tooltip events on the chart
+			if (this.options.showTooltips){
+				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+					var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
+
+					this.eachBars(function(bar){
+						bar.restore(['fillColor', 'strokeColor']);
+					});
+					helpers.each(activeBars, function(activeBar){
+						activeBar.fillColor = activeBar.highlightFill;
+						activeBar.strokeColor = activeBar.highlightStroke;
+					});
+					this.showTooltip(activeBars);
+				});
+			}
+
+			//Declare the extension of the default point, to cater for the options passed in to the constructor
+			this.BarClass = Chart.Rectangle.extend({
+				strokeWidth : this.options.barStrokeWidth,
+				showStroke : this.options.barShowStroke,
+				ctx : this.chart.ctx
+			});
+
+			//Iterate through each of the datasets, and build this into a property of the chart
+			helpers.each(data.datasets,function(dataset,datasetIndex){
+
+				var datasetObject = {
+					label : dataset.label || null,
+					fillColor : dataset.fillColor,
+					strokeColor : dataset.strokeColor,
+					bars : []
+				};
+
+				this.datasets.push(datasetObject);
+
+				helpers.each(dataset.data,function(dataPoint,index){
+					//Add a new point for each piece of data, passing any required data to draw.
+					datasetObject.bars.push(new this.BarClass({
+						value : dataPoint,
+						label : data.labels[index],
+						datasetLabel: dataset.label,
+						strokeColor : dataset.strokeColor,
+						fillColor : dataset.fillColor,
+						highlightFill : dataset.highlightFill || dataset.fillColor,
+						highlightStroke : dataset.highlightStroke || dataset.strokeColor
+					}));
+				},this);
+
+			},this);
+
+			this.buildScale(data.labels);
+
+			this.BarClass.prototype.base = this.scale.endPoint;
+
+			this.eachBars(function(bar, index, datasetIndex){
+				helpers.extend(bar, {
+					width : this.scale.calculateBarWidth(this.datasets.length),
+					x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
+					y: this.scale.endPoint
+				});
+				bar.save();
+			}, this);
+
+			this.render();
+		},
+		update : function(){
+			this.scale.update();
+			// Reset any highlight colours before updating.
+			helpers.each(this.activeElements, function(activeElement){
+				activeElement.restore(['fillColor', 'strokeColor']);
+			});
+
+			this.eachBars(function(bar){
+				bar.save();
+			});
+			this.render();
+		},
+		eachBars : function(callback){
+			helpers.each(this.datasets,function(dataset, datasetIndex){
+				helpers.each(dataset.bars, callback, this, datasetIndex);
+			},this);
+		},
+		getBarsAtEvent : function(e){
+			var barsArray = [],
+				eventPosition = helpers.getRelativePosition(e),
+				datasetIterator = function(dataset){
+					barsArray.push(dataset.bars[barIndex]);
+				},
+				barIndex;
+
+			for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
+				for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
+					if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){
+						helpers.each(this.datasets, datasetIterator);
+						return barsArray;
+					}
+				}
+			}
+
+			return barsArray;
+		},
+		buildScale : function(labels){
+			var self = this;
+
+			var dataTotal = function(){
+				var values = [];
+				self.eachBars(function(bar){
+					values.push(bar.value);
+				});
+				return values;
+			};
+
+			var scaleOptions = {
+				templateString : this.options.scaleLabel,
+				height : this.chart.height,
+				width : this.chart.width,
+				ctx : this.chart.ctx,
+				textColor : this.options.scaleFontColor,
+				fontSize : this.options.scaleFontSize,
+				fontStyle : this.options.scaleFontStyle,
+				fontFamily : this.options.scaleFontFamily,
+				valuesCount : labels.length,
+				beginAtZero : this.options.scaleBeginAtZero,
+				integersOnly : this.options.scaleIntegersOnly,
+				calculateYRange: function(currentHeight){
+					var updatedRanges = helpers.calculateScaleRange(
+						dataTotal(),
+						currentHeight,
+						this.fontSize,
+						this.beginAtZero,
+						this.integersOnly
+					);
+					helpers.extend(this, updatedRanges);
+				},
+				xLabels : labels,
+				font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
+				lineWidth : this.options.scaleLineWidth,
+				lineColor : this.options.scaleLineColor,
+				showHorizontalLines : this.options.scaleShowHorizontalLines,
+				showVerticalLines : this.options.scaleShowVerticalLines,
+				gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
+				gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
+				padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
+				showLabels : this.options.scaleShowLabels,
+				display : this.options.showScale
+			};
+
+			if (this.options.scaleOverride){
+				helpers.extend(scaleOptions, {
+					calculateYRange: helpers.noop,
+					steps: this.options.scaleSteps,
+					stepValue: this.options.scaleStepWidth,
+					min: this.options.scaleStartValue,
+					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+				});
+			}
+
+			this.scale = new this.ScaleClass(scaleOptions);
+		},
+		addData : function(valuesArray,label){
+			//Map the values array for each of the datasets
+			helpers.each(valuesArray,function(value,datasetIndex){
+				//Add a new point for each piece of data, passing any required data to draw.
+				this.datasets[datasetIndex].bars.push(new this.BarClass({
+					value : value,
+					label : label,
+					x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
+					y: this.scale.endPoint,
+					width : this.scale.calculateBarWidth(this.datasets.length),
+					base : this.scale.endPoint,
+					strokeColor : this.datasets[datasetIndex].strokeColor,
+					fillColor : this.datasets[datasetIndex].fillColor
+				}));
+			},this);
+
+			this.scale.addXLabel(label);
+			//Then re-render the chart.
+			this.update();
+		},
+		removeData : function(){
+			this.scale.removeXLabel();
+			//Then re-render the chart.
+			helpers.each(this.datasets,function(dataset){
+				dataset.bars.shift();
+			},this);
+			this.update();
+		},
+		reflow : function(){
+			helpers.extend(this.BarClass.prototype,{
+				y: this.scale.endPoint,
+				base : this.scale.endPoint
+			});
+			var newScaleProps = helpers.extend({
+				height : this.chart.height,
+				width : this.chart.width
+			});
+			this.scale.update(newScaleProps);
+		},
+		draw : function(ease){
+			var easingDecimal = ease || 1;
+			this.clear();
+
+			var ctx = this.chart.ctx;
+
+			this.scale.draw(easingDecimal);
+
+			//Draw all the bars for each dataset
+			helpers.each(this.datasets,function(dataset,datasetIndex){
+				helpers.each(dataset.bars,function(bar,index){
+					if (bar.hasValue()){
+						bar.base = this.scale.endPoint;
+						//Transition then draw
+						bar.transition({
+							x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
+							y : this.scale.calculateY(bar.value),
+							width : this.scale.calculateBarWidth(this.datasets.length)
+						}, easingDecimal).draw();
+					}
+				},this);
+
+			},this);
+		}
+	});
+
+
+}).call(this);
+
+(function(){
+	"use strict";
+
+	var root = this,
+		Chart = root.Chart,
+		//Cache a local reference to Chart.helpers
+		helpers = Chart.helpers;
+
+	var defaultConfig = {
+		//Boolean - Whether we should show a stroke on each segment
+		segmentShowStroke : true,
+
+		//String - The colour of each segment stroke
+		segmentStrokeColor : "#fff",
+
+		//Number - The width of each segment stroke
+		segmentStrokeWidth : 2,
+
+		//The percentage of the chart that we cut out of the middle.
+		percentageInnerCutout : 50,
+
+		//Number - Amount of animation steps
+		animationSteps : 100,
+
+		//String - Animation easing effect
+		animationEasing : "easeOutBounce",
+
+		//Boolean - Whether we animate the rotation of the Doughnut
+		animateRotate : true,
+
+		//Boolean - Whether we animate scaling the Doughnut from the centre
+		animateScale : false,
+
+		//String - A legend template
+		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
+
+	};
+
+
+	Chart.Type.extend({
+		//Passing in a name registers this chart in the Chart namespace
+		name: "Doughnut",
+		//Providing a defaults will also register the deafults in the chart namespace
+		defaults : defaultConfig,
+		//Initialize is fired when the chart is initialized - Data is passed in as a parameter
+		//Config is automatically merged by the core of Chart.js, and is available at this.options
+		initialize:  function(data){
+
+			//Declare segments as a static property to prevent inheriting across the Chart type prototype
+			this.segments = [];
+			this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) -	this.options.segmentStrokeWidth/2)/2;
+
+			this.SegmentArc = Chart.Arc.extend({
+				ctx : this.chart.ctx,
+				x : this.chart.width/2,
+				y : this.chart.height/2
+			});
+
+			//Set up tooltip events on the chart
+			if (this.options.showTooltips){
+				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+					var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
+
+					helpers.each(this.segments,function(segment){
+						segment.restore(["fillColor"]);
+					});
+					helpers.each(activeSegments,function(activeSegment){
+						activeSegment.fillColor = activeSegment.highlightColor;
+					});
+					this.showTooltip(activeSegments);
+				});
+			}
+			this.calculateTotal(data);
+
+			helpers.each(data,function(datapoint, index){
+				this.addData(datapoint, index, true);
+			},this);
+
+			this.render();
+		},
+		getSegmentsAtEvent : function(e){
+			var segmentsArray = [];
+
+			var location = helpers.getRelativePosition(e);
+
+			helpers.each(this.segments,function(segment){
+				if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
+			},this);
+			return segmentsArray;
+		},
+		addData : function(segment, atIndex, silent){
+			var index = atIndex || this.segments.length;
+			this.segments.splice(index, 0, new this.SegmentArc({
+				value : segment.value,
+				outerRadius : (this.options.animateScale) ? 0 : this.outerRadius,
+				innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout,
+				fillColor : segment.color,
+				highlightColor : segment.highlight || segment.color,
+				showStroke : this.options.segmentShowStroke,
+				strokeWidth : this.options.segmentStrokeWidth,
+				strokeColor : this.options.segmentStrokeColor,
+				startAngle : Math.PI * 1.5,
+				circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
+				label : segment.label
+			}));
+			if (!silent){
+				this.reflow();
+				this.update();
+			}
+		},
+		calculateCircumference : function(value){
+			return (Math.PI*2)*(Math.abs(value) / this.total);
+		},
+		calculateTotal : function(data){
+			this.total = 0;
+			helpers.each(data,function(segment){
+				this.total += Math.abs(segment.value);
+			},this);
+		},
+		update : function(){
+			this.calculateTotal(this.segments);
+
+			// Reset any highlight colours before updating.
+			helpers.each(this.activeElements, function(activeElement){
+				activeElement.restore(['fillColor']);
+			});
+
+			helpers.each(this.segments,function(segment){
+				segment.save();
+			});
+			this.render();
+		},
+
+		removeData: function(atIndex){
+			var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
+			this.segments.splice(indexToDelete, 1);
+			this.reflow();
+			this.update();
+		},
+
+		reflow : function(){
+			helpers.extend(this.SegmentArc.prototype,{
+				x : this.chart.width/2,
+				y : this.chart.height/2
+			});
+			this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) -	this.options.segmentStrokeWidth/2)/2;
+			helpers.each(this.segments, function(segment){
+				segment.update({
+					outerRadius : this.outerRadius,
+					innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
+				});
+			}, this);
+		},
+		draw : function(easeDecimal){
+			var animDecimal = (easeDecimal) ? easeDecimal : 1;
+			this.clear();
+			helpers.each(this.segments,function(segment,index){
+				segment.transition({
+					circumference : this.calculateCircumference(segment.value),
+					outerRadius : this.outerRadius,
+					innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
+				},animDecimal);
+
+				segment.endAngle = segment.startAngle + segment.circumference;
+
+				segment.draw();
+				if (index === 0){
+					segment.startAngle = Math.PI * 1.5;
+				}
+				//Check to see if it's the last segment, if not get the next and update the start angle
+				if (index < this.segments.length-1){
+					this.segments[index+1].startAngle = segment.endAngle;
+				}
+			},this);
+
+		}
+	});
+
+	Chart.types.Doughnut.extend({
+		name : "Pie",
+		defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0})
+	});
+
+}).call(this);
+(function(){
+	"use strict";
+
+	var root = this,
+		Chart = root.Chart,
+		helpers = Chart.helpers;
+
+	var defaultConfig = {
+
+		///Boolean - Whether grid lines are shown across the chart
+		scaleShowGridLines : true,
+
+		//String - Colour of the grid lines
+		scaleGridLineColor : "rgba(0,0,0,.05)",
+
+		//Number - Width of the grid lines
+		scaleGridLineWidth : 1,
+
+		//Boolean - Whether to show horizontal lines (except X axis)
+		scaleShowHorizontalLines: true,
+
+		//Boolean - Whether to show vertical lines (except Y axis)
+		scaleShowVerticalLines: true,
+
+		//Boolean - Whether the line is curved between points
+		bezierCurve : true,
+
+		//Number - Tension of the bezier curve between points
+		bezierCurveTension : 0.4,
+
+		//Boolean - Whether to show a dot for each point
+		pointDot : true,
+
+		//Number - Radius of each point dot in pixels
+		pointDotRadius : 4,
+
+		//Number - Pixel width of point dot stroke
+		pointDotStrokeWidth : 1,
+
+		//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
+		pointHitDetectionRadius : 20,
+
+		//Boolean - Whether to show a stroke for datasets
+		datasetStroke : true,
+
+		//Number - Pixel width of dataset stroke
+		datasetStrokeWidth : 2,
+
+		//Boolean - Whether to fill the dataset with a colour
+		datasetFill : true,
+
+		//String - A legend template
+		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
+
+	};
+
+
+	Chart.Type.extend({
+		name: "Line",
+		defaults : defaultConfig,
+		initialize:  function(data){
+			//Declare the extension of the default point, to cater for the options passed in to the constructor
+			this.PointClass = Chart.Point.extend({
+				strokeWidth : this.options.pointDotStrokeWidth,
+				radius : this.options.pointDotRadius,
+				display: this.options.pointDot,
+				hitDetectionRadius : this.options.pointHitDetectionRadius,
+				ctx : this.chart.ctx,
+				inRange : function(mouseX){
+					return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
+				}
+			});
+
+			this.datasets = [];
+
+			//Set up tooltip events on the chart
+			if (this.options.showTooltips){
+				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+					var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
+					this.eachPoints(function(point){
+						point.restore(['fillColor', 'strokeColor']);
+					});
+					helpers.each(activePoints, function(activePoint){
+						activePoint.fillColor = activePoint.highlightFill;
+						activePoint.strokeColor = activePoint.highlightStroke;
+					});
+					this.showTooltip(activePoints);
+				});
+			}
+
+			//Iterate through each of the datasets, and build this into a property of the chart
+			helpers.each(data.datasets,function(dataset){
+
+				var datasetObject = {
+					label : dataset.label || null,
+					fillColor : dataset.fillColor,
+					strokeColor : dataset.strokeColor,
+					pointColor : dataset.pointColor,
+					pointStrokeColor : dataset.pointStrokeColor,
+					points : []
+				};
+
+				this.datasets.push(datasetObject);
+
+
+				helpers.each(dataset.data,function(dataPoint,index){
+					//Add a new point for each piece of data, passing any required data to draw.
+					datasetObject.points.push(new this.PointClass({
+						value : dataPoint,
+						label : data.labels[index],
+						datasetLabel: dataset.label,
+						strokeColor : dataset.pointStrokeColor,
+						fillColor : dataset.pointColor,
+						highlightFill : dataset.pointHighlightFill || dataset.pointColor,
+						highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
+					}));
+				},this);
+
+				this.buildScale(data.labels);
+
+
+				this.eachPoints(function(point, index){
+					helpers.extend(point, {
+						x: this.scale.calculateX(index),
+						y: this.scale.endPoint
+					});
+					point.save();
+				}, this);
+
+			},this);
+
+
+			this.render();
+		},
+		update : function(){
+			this.scale.update();
+			// Reset any highlight colours before updating.
+			helpers.each(this.activeElements, function(activeElement){
+				activeElement.restore(['fillColor', 'strokeColor']);
+			});
+			this.eachPoints(function(point){
+				point.save();
+			});
+			this.render();
+		},
+		eachPoints : function(callback){
+			helpers.each(this.datasets,function(dataset){
+				helpers.each(dataset.points,callback,this);
+			},this);
+		},
+		getPointsAtEvent : function(e){
+			var pointsArray = [],
+				eventPosition = helpers.getRelativePosition(e);
+			helpers.each(this.datasets,function(dataset){
+				helpers.each(dataset.points,function(point){
+					if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
+				});
+			},this);
+			return pointsArray;
+		},
+		buildScale : function(labels){
+			var self = this;
+
+			var dataTotal = function(){
+				var values = [];
+				self.eachPoints(function(point){
+					values.push(point.value);
+				});
+
+				return values;
+			};
+
+			var scaleOptions = {
+				templateString : this.options.scaleLabel,
+				height : this.chart.height,
+				width : this.chart.width,
+				ctx : this.chart.ctx,
+				textColor : this.options.scaleFontColor,
+				fontSize : this.options.scaleFontSize,
+				fontStyle : this.options.scaleFontStyle,
+				fontFamily : this.options.scaleFontFamily,
+				valuesCount : labels.length,
+				beginAtZero : this.options.scaleBeginAtZero,
+				integersOnly : this.options.scaleIntegersOnly,
+				calculateYRange : function(currentHeight){
+					var updatedRanges = helpers.calculateScaleRange(
+						dataTotal(),
+						currentHeight,
+						this.fontSize,
+						this.beginAtZero,
+						this.integersOnly
+					);
+					helpers.extend(this, updatedRanges);
+				},
+				xLabels : labels,
+				font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
+				lineWidth : this.options.scaleLineWidth,
+				lineColor : this.options.scaleLineColor,
+				showHorizontalLines : this.options.scaleShowHorizontalLines,
+				showVerticalLines : this.options.scaleShowVerticalLines,
+				gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
+				gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
+				padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
+				showLabels : this.options.scaleShowLabels,
+				display : this.options.showScale
+			};
+
+			if (this.options.scaleOverride){
+				helpers.extend(scaleOptions, {
+					calculateYRange: helpers.noop,
+					steps: this.options.scaleSteps,
+					stepValue: this.options.scaleStepWidth,
+					min: this.options.scaleStartValue,
+					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+				});
+			}
+
+
+			this.scale = new Chart.Scale(scaleOptions);
+		},
+		addData : function(valuesArray,label){
+			//Map the values array for each of the datasets
+
+			helpers.each(valuesArray,function(value,datasetIndex){
+				//Add a new point for each piece of data, passing any required data to draw.
+				this.datasets[datasetIndex].points.push(new this.PointClass({
+					value : value,
+					label : label,
+					x: this.scale.calculateX(this.scale.valuesCount+1),
+					y: this.scale.endPoint,
+					strokeColor : this.datasets[datasetIndex].pointStrokeColor,
+					fillColor : this.datasets[datasetIndex].pointColor
+				}));
+			},this);
+
+			this.scale.addXLabel(label);
+			//Then re-render the chart.
+			this.update();
+		},
+		removeData : function(){
+			this.scale.removeXLabel();
+			//Then re-render the chart.
+			helpers.each(this.datasets,function(dataset){
+				dataset.points.shift();
+			},this);
+			this.update();
+		},
+		reflow : function(){
+			var newScaleProps = helpers.extend({
+				height : this.chart.height,
+				width : this.chart.width
+			});
+			this.scale.update(newScaleProps);
+		},
+		draw : function(ease){
+			var easingDecimal = ease || 1;
+			this.clear();
+
+			var ctx = this.chart.ctx;
+
+			// Some helper methods for getting the next/prev points
+			var hasValue = function(item){
+				return item.value !== null;
+			},
+			nextPoint = function(point, collection, index){
+				return helpers.findNextWhere(collection, hasValue, index) || point;
+			},
+			previousPoint = function(point, collection, index){
+				return helpers.findPreviousWhere(collection, hasValue, index) || point;
+			};
+
+			this.scale.draw(easingDecimal);
+
+
+			helpers.each(this.datasets,function(dataset){
+				var pointsWithValues = helpers.where(dataset.points, hasValue);
+
+				//Transition each point first so that the line and point drawing isn't out of sync
+				//We can use this extra loop to calculate the control points of this dataset also in this loop
+
+				helpers.each(dataset.points, function(point, index){
+					if (point.hasValue()){
+						point.transition({
+							y : this.scale.calculateY(point.value),
+							x : this.scale.calculateX(index)
+						}, easingDecimal);
+					}
+				},this);
+
+
+				// Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
+				// This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
+				if (this.options.bezierCurve){
+					helpers.each(pointsWithValues, function(point, index){
+						var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
+						point.controlPoints = helpers.splineCurve(
+							previousPoint(point, pointsWithValues, index),
+							point,
+							nextPoint(point, pointsWithValues, index),
+							tension
+						);
+
+						// Prevent the bezier going outside of the bounds of the graph
+
+						// Cap puter bezier handles to the upper/lower scale bounds
+						if (point.controlPoints.outer.y > this.scale.endPoint){
+							point.controlPoints.outer.y = this.scale.endPoint;
+						}
+						else if (point.controlPoints.outer.y < this.scale.startPoint){
+							point.controlPoints.outer.y = this.scale.startPoint;
+						}
+
+						// Cap inner bezier handles to the upper/lower scale bounds
+						if (point.controlPoints.inner.y > this.scale.endPoint){
+							point.controlPoints.inner.y = this.scale.endPoint;
+						}
+						else if (point.controlPoints.inner.y < this.scale.startPoint){
+							point.controlPoints.inner.y = this.scale.startPoint;
+						}
+					},this);
+				}
+
+
+				//Draw the line between all the points
+				ctx.lineWidth = this.options.datasetStrokeWidth;
+				ctx.strokeStyle = dataset.strokeColor;
+				ctx.beginPath();
+
+				helpers.each(pointsWithValues, function(point, index){
+					if (index === 0){
+						ctx.moveTo(point.x, point.y);
+					}
+					else{
+						if(this.options.bezierCurve){
+							var previous = previousPoint(point, pointsWithValues, index);
+
+							ctx.bezierCurveTo(
+								previous.controlPoints.outer.x,
+								previous.controlPoints.outer.y,
+								point.controlPoints.inner.x,
+								point.controlPoints.inner.y,
+								point.x,
+								point.y
+							);
+						}
+						else{
+							ctx.lineTo(point.x,point.y);
+						}
+					}
+				}, this);
+
+				ctx.stroke();
+
+				if (this.options.datasetFill && pointsWithValues.length > 0){
+					//Round off the line by going to the base of the chart, back to the start, then fill.
+					ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
+					ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
+					ctx.fillStyle = dataset.fillColor;
+					ctx.closePath();
+					ctx.fill();
+				}
+
+				//Now draw the points over the line
+				//A little inefficient double looping, but better than the line
+				//lagging behind the point positions
+				helpers.each(pointsWithValues,function(point){
+					point.draw();
+				});
+			},this);
+		}
+	});
+
+
+}).call(this);
+
+(function(){
+	"use strict";
+
+	var root = this,
+		Chart = root.Chart,
+		//Cache a local reference to Chart.helpers
+		helpers = Chart.helpers;
+
+	var defaultConfig = {
+		//Boolean - Show a backdrop to the scale label
+		scaleShowLabelBackdrop : true,
+
+		//String - The colour of the label backdrop
+		scaleBackdropColor : "rgba(255,255,255,0.75)",
+
+		// Boolean - Whether the scale should begin at zero
+		scaleBeginAtZero : true,
+
+		//Number - The backdrop padding above & below the label in pixels
+		scaleBackdropPaddingY : 2,
+
+		//Number - The backdrop padding to the side of the label in pixels
+		scaleBackdropPaddingX : 2,
+
+		//Boolean - Show line for each value in the scale
+		scaleShowLine : true,
+
+		//Boolean - Stroke a line around each segment in the chart
+		segmentShowStroke : true,
+
+		//String - The colour of the stroke on each segement.
+		segmentStrokeColor : "#fff",
+
+		//Number - The width of the stroke value in pixels
+		segmentStrokeWidth : 2,
+
+		//Number - Amount of animation steps
+		animationSteps : 100,
+
+		//String - Animation easing effect.
+		animationEasing : "easeOutBounce",
+
+		//Boolean - Whether to animate the rotation of the chart
+		animateRotate : true,
+
+		//Boolean - Whether to animate scaling the chart from the centre
+		animateScale : false,
+
+		//String - A legend template
+		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
+	};
+
+
+	Chart.Type.extend({
+		//Passing in a name registers this chart in the Chart namespace
+		name: "PolarArea",
+		//Providing a defaults will also register the deafults in the chart namespace
+		defaults : defaultConfig,
+		//Initialize is fired when the chart is initialized - Data is passed in as a parameter
+		//Config is automatically merged by the core of Chart.js, and is available at this.options
+		initialize:  function(data){
+			this.segments = [];
+			//Declare segment class as a chart instance specific class, so it can share props for this instance
+			this.SegmentArc = Chart.Arc.extend({
+				showStroke : this.options.segmentShowStroke,
+				strokeWidth : this.options.segmentStrokeWidth,
+				strokeColor : this.options.segmentStrokeColor,
+				ctx : this.chart.ctx,
+				innerRadius : 0,
+				x : this.chart.width/2,
+				y : this.chart.height/2
+			});
+			this.scale = new Chart.RadialScale({
+				display: this.options.showScale,
+				fontStyle: this.options.scaleFontStyle,
+				fontSize: this.options.scaleFontSize,
+				fontFamily: this.options.scaleFontFamily,
+				fontColor: this.options.scaleFontColor,
+				showLabels: this.options.scaleShowLabels,
+				showLabelBackdrop: this.options.scaleShowLabelBackdrop,
+				backdropColor: this.options.scaleBackdropColor,
+				backdropPaddingY : this.options.scaleBackdropPaddingY,
+				backdropPaddingX: this.options.scaleBackdropPaddingX,
+				lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
+				lineColor: this.options.scaleLineColor,
+				lineArc: true,
+				width: this.chart.width,
+				height: this.chart.height,
+				xCenter: this.chart.width/2,
+				yCenter: this.chart.height/2,
+				ctx : this.chart.ctx,
+				templateString: this.options.scaleLabel,
+				valuesCount: data.length
+			});
+
+			this.updateScaleRange(data);
+
+			this.scale.update();
+
+			helpers.each(data,function(segment,index){
+				this.addData(segment,index,true);
+			},this);
+
+			//Set up tooltip events on the chart
+			if (this.options.showTooltips){
+				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+					var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
+					helpers.each(this.segments,function(segment){
+						segment.restore(["fillColor"]);
+					});
+					helpers.each(activeSegments,function(activeSegment){
+						activeSegment.fillColor = activeSegment.highlightColor;
+					});
+					this.showTooltip(activeSegments);
+				});
+			}
+
+			this.render();
+		},
+		getSegmentsAtEvent : function(e){
+			var segmentsArray = [];
+
+			var location = helpers.getRelativePosition(e);
+
+			helpers.each(this.segments,function(segment){
+				if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
+			},this);
+			return segmentsArray;
+		},
+		addData : function(segment, atIndex, silent){
+			var index = atIndex || this.segments.length;
+
+			this.segments.splice(index, 0, new this.SegmentArc({
+				fillColor: segment.color,
+				highlightColor: segment.highlight || segment.color,
+				label: segment.label,
+				value: segment.value,
+				outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),
+				circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),
+				startAngle: Math.PI * 1.5
+			}));
+			if (!silent){
+				this.reflow();
+				this.update();
+			}
+		},
+		removeData: function(atIndex){
+			var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
+			this.segments.splice(indexToDelete, 1);
+			this.reflow();
+			this.update();
+		},
+		calculateTotal: function(data){
+			this.total = 0;
+			helpers.each(data,function(segment){
+				this.total += segment.value;
+			},this);
+			this.scale.valuesCount = this.segments.length;
+		},
+		updateScaleRange: function(datapoints){
+			var valuesArray = [];
+			helpers.each(datapoints,function(segment){
+				valuesArray.push(segment.value);
+			});
+
+			var scaleSizes = (this.options.scaleOverride) ?
+				{
+					steps: this.options.scaleSteps,
+					stepValue: this.options.scaleStepWidth,
+					min: this.options.scaleStartValue,
+					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+				} :
+				helpers.calculateScaleRange(
+					valuesArray,
+					helpers.min([this.chart.width, this.chart.height])/2,
+					this.options.scaleFontSize,
+					this.options.scaleBeginAtZero,
+					this.options.scaleIntegersOnly
+				);
+
+			helpers.extend(
+				this.scale,
+				scaleSizes,
+				{
+					size: helpers.min([this.chart.width, this.chart.height]),
+					xCenter: this.chart.width/2,
+					yCenter: this.chart.height/2
+				}
+			);
+
+		},
+		update : function(){
+			this.calculateTotal(this.segments);
+
+			helpers.each(this.segments,function(segment){
+				segment.save();
+			});
+			
+			this.reflow();
+			this.render();
+		},
+		reflow : function(){
+			helpers.extend(this.SegmentArc.prototype,{
+				x : this.chart.width/2,
+				y : this.chart.height/2
+			});
+			this.updateScaleRange(this.segments);
+			this.scale.update();
+
+			helpers.extend(this.scale,{
+				xCenter: this.chart.width/2,
+				yCenter: this.chart.height/2
+			});
+
+			helpers.each(this.segments, function(segment){
+				segment.update({
+					outerRadius : this.scale.calculateCenterOffset(segment.value)
+				});
+			}, this);
+
+		},
+		draw : function(ease){
+			var easingDecimal = ease || 1;
+			//Clear & draw the canvas
+			this.clear();
+			helpers.each(this.segments,function(segment, index){
+				segment.transition({
+					circumference : this.scale.getCircumference(),
+					outerRadius : this.scale.calculateCenterOffset(segment.value)
+				},easingDecimal);
+
+				segment.endAngle = segment.startAngle + segment.circumference;
+
+				// If we've removed the first segment we need to set the first one to
+				// start at the top.
+				if (index === 0){
+					segment.startAngle = Math.PI * 1.5;
+				}
+
+				//Check to see if it's the last segment, if not get the next and update the start angle
+				if (index < this.segments.length - 1){
+					this.segments[index+1].startAngle = segment.endAngle;
+				}
+				segment.draw();
+			}, this);
+			this.scale.draw();
+		}
+	});
+
+}).call(this);
+(function(){
+	"use strict";
+
+	var root = this,
+		Chart = root.Chart,
+		helpers = Chart.helpers;
+
+
+
+	Chart.Type.extend({
+		name: "Radar",
+		defaults:{
+			//Boolean - Whether to show lines for each scale point
+			scaleShowLine : true,
+
+			//Boolean - Whether we show the angle lines out of the radar
+			angleShowLineOut : true,
+
+			//Boolean - Whether to show labels on the scale
+			scaleShowLabels : false,
+
+			// Boolean - Whether the scale should begin at zero
+			scaleBeginAtZero : true,
+
+			//String - Colour of the angle line
+			angleLineColor : "rgba(0,0,0,.1)",
+
+			//Number - Pixel width of the angle line
+			angleLineWidth : 1,
+
+			//String - Point label font declaration
+			pointLabelFontFamily : "'Arial'",
+
+			//String - Point label font weight
+			pointLabelFontStyle : "normal",
+
+			//Number - Point label font size in pixels
+			pointLabelFontSize : 10,
+
+			//String - Point label font colour
+			pointLabelFontColor : "#666",
+
+			//Boolean - Whether to show a dot for each point
+			pointDot : true,
+
+			//Number - Radius of each point dot in pixels
+			pointDotRadius : 3,
+
+			//Number - Pixel width of point dot stroke
+			pointDotStrokeWidth : 1,
+
+			//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
+			pointHitDetectionRadius : 20,
+
+			//Boolean - Whether to show a stroke for datasets
+			datasetStroke : true,
+
+			//Number - Pixel width of dataset stroke
+			datasetStrokeWidth : 2,
+
+			//Boolean - Whether to fill the dataset with a colour
+			datasetFill : true,
+
+			//String - A legend template
+			legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
+
+		},
+
+		initialize: function(data){
+			this.PointClass = Chart.Point.extend({
+				strokeWidth : this.options.pointDotStrokeWidth,
+				radius : this.options.pointDotRadius,
+				display: this.options.pointDot,
+				hitDetectionRadius : this.options.pointHitDetectionRadius,
+				ctx : this.chart.ctx
+			});
+
+			this.datasets = [];
+
+			this.buildScale(data);
+
+			//Set up tooltip events on the chart
+			if (this.options.showTooltips){
+				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+					var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
+
+					this.eachPoints(function(point){
+						point.restore(['fillColor', 'strokeColor']);
+					});
+					helpers.each(activePointsCollection, function(activePoint){
+						activePoint.fillColor = activePoint.highlightFill;
+						activePoint.strokeColor = activePoint.highlightStroke;
+					});
+
+					this.showTooltip(activePointsCollection);
+				});
+			}
+
+			//Iterate through each of the datasets, and build this into a property of the chart
+			helpers.each(data.datasets,function(dataset){
+
+				var datasetObject = {
+					label: dataset.label || null,
+					fillColor : dataset.fillColor,
+					strokeColor : dataset.strokeColor,
+					pointColor : dataset.pointColor,
+					pointStrokeColor : dataset.pointStrokeColor,
+					points : []
+				};
+
+				this.datasets.push(datasetObject);
+
+				helpers.each(dataset.data,function(dataPoint,index){
+					//Add a new point for each piece of data, passing any required data to draw.
+					var pointPosition;
+					if (!this.scale.animation){
+						pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
+					}
+					datasetObject.points.push(new this.PointClass({
+						value : dataPoint,
+						label : data.labels[index],
+						datasetLabel: dataset.label,
+						x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
+						y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
+						strokeColor : dataset.pointStrokeColor,
+						fillColor : dataset.pointColor,
+						highlightFill : dataset.pointHighlightFill || dataset.pointColor,
+						highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
+					}));
+				},this);
+
+			},this);
+
+			this.render();
+		},
+		eachPoints : function(callback){
+			helpers.each(this.datasets,function(dataset){
+				helpers.each(dataset.points,callback,this);
+			},this);
+		},
+
+		getPointsAtEvent : function(evt){
+			var mousePosition = helpers.getRelativePosition(evt),
+				fromCenter = helpers.getAngleFromPoint({
+					x: this.scale.xCenter,
+					y: this.scale.yCenter
+				}, mousePosition);
+
+			var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,
+				pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
+				activePointsCollection = [];
+
+			// If we're at the top, make the pointIndex 0 to get the first of the array.
+			if (pointIndex >= this.scale.valuesCount || pointIndex < 0){
+				pointIndex = 0;
+			}
+
+			if (fromCenter.distance <= this.scale.drawingArea){
+				helpers.each(this.datasets, function(dataset){
+					activePointsCollection.push(dataset.points[pointIndex]);
+				});
+			}
+
+			return activePointsCollection;
+		},
+
+		buildScale : function(data){
+			this.scale = new Chart.RadialScale({
+				display: this.options.showScale,
+				fontStyle: this.options.scaleFontStyle,
+				fontSize: this.options.scaleFontSize,
+				fontFamily: this.options.scaleFontFamily,
+				fontColor: this.options.scaleFontColor,
+				showLabels: this.options.scaleShowLabels,
+				showLabelBackdrop: this.options.scaleShowLabelBackdrop,
+				backdropColor: this.options.scaleBackdropColor,
+				backdropPaddingY : this.options.scaleBackdropPaddingY,
+				backdropPaddingX: this.options.scaleBackdropPaddingX,
+				lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
+				lineColor: this.options.scaleLineColor,
+				angleLineColor : this.options.angleLineColor,
+				angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
+				// Point labels at the edge of each line
+				pointLabelFontColor : this.options.pointLabelFontColor,
+				pointLabelFontSize : this.options.pointLabelFontSize,
+				pointLabelFontFamily : this.options.pointLabelFontFamily,
+				pointLabelFontStyle : this.options.pointLabelFontStyle,
+				height : this.chart.height,
+				width: this.chart.width,
+				xCenter: this.chart.width/2,
+				yCenter: this.chart.height/2,
+				ctx : this.chart.ctx,
+				templateString: this.options.scaleLabel,
+				labels: data.labels,
+				valuesCount: data.datasets[0].data.length
+			});
+
+			this.scale.setScaleSize();
+			this.updateScaleRange(data.datasets);
+			this.scale.buildYLabels();
+		},
+		updateScaleRange: function(datasets){
+			var valuesArray = (function(){
+				var totalDataArray = [];
+				helpers.each(datasets,function(dataset){
+					if (dataset.data){
+						totalDataArray = totalDataArray.concat(dataset.data);
+					}
+					else {
+						helpers.each(dataset.points, function(point){
+							totalDataArray.push(point.value);
+						});
+					}
+				});
+				return totalDataArray;
+			})();
+
+
+			var scaleSizes = (this.options.scaleOverride) ?
+				{
+					steps: this.options.scaleSteps,
+					stepValue: this.options.scaleStepWidth,
+					min: this.options.scaleStartValue,
+					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+				} :
+				helpers.calculateScaleRange(
+					valuesArray,
+					helpers.min([this.chart.width, this.chart.height])/2,
+					this.options.scaleFontSize,
+					this.options.scaleBeginAtZero,
+					this.options.scaleIntegersOnly
+				);
+
+			helpers.extend(
+				this.scale,
+				scaleSizes
+			);
+
+		},
+		addData : function(valuesArray,label){
+			//Map the values array for each of the datasets
+			this.scale.valuesCount++;
+			helpers.each(valuesArray,function(value,datasetIndex){
+				var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
+				this.datasets[datasetIndex].points.push(new this.PointClass({
+					value : value,
+					label : label,
+					x: pointPosition.x,
+					y: pointPosition.y,
+					strokeColor : this.datasets[datasetIndex].pointStrokeColor,
+					fillColor : this.datasets[datasetIndex].pointColor
+				}));
+			},this);
+
+			this.scale.labels.push(label);
+
+			this.reflow();
+
+			this.update();
+		},
+		removeData : function(){
+			this.scale.valuesCount--;
+			this.scale.labels.shift();
+			helpers.each(this.datasets,function(dataset){
+				dataset.points.shift();
+			},this);
+			this.reflow();
+			this.update();
+		},
+		update : function(){
+			this.eachPoints(function(point){
+				point.save();
+			});
+			this.reflow();
+			this.render();
+		},
+		reflow: function(){
+			helpers.extend(this.scale, {
+				width : this.chart.width,
+				height: this.chart.height,
+				size : helpers.min([this.chart.width, this.chart.height]),
+				xCenter: this.chart.width/2,
+				yCenter: this.chart.height/2
+			});
+			this.updateScaleRange(this.datasets);
+			this.scale.setScaleSize();
+			this.scale.buildYLabels();
+		},
+		draw : function(ease){
+			var easeDecimal = ease || 1,
+				ctx = this.chart.ctx;
+			this.clear();
+			this.scale.draw();
+
+			helpers.each(this.datasets,function(dataset){
+
+				//Transition each point first so that the line and point drawing isn't out of sync
+				helpers.each(dataset.points,function(point,index){
+					if (point.hasValue()){
+						point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
+					}
+				},this);
+
+
+
+				//Draw the line between all the points
+				ctx.lineWidth = this.options.datasetStrokeWidth;
+				ctx.strokeStyle = dataset.strokeColor;
+				ctx.beginPath();
+				helpers.each(dataset.points,function(point,index){
+					if (index === 0){
+						ctx.moveTo(point.x,point.y);
+					}
+					else{
+						ctx.lineTo(point.x,point.y);
+					}
+				},this);
+				ctx.closePath();
+				ctx.stroke();
+
+				ctx.fillStyle = dataset.fillColor;
+				ctx.fill();
+
+				//Now draw the points over the line
+				//A little inefficient double looping, but better than the line
+				//lagging behind the point positions
+				helpers.each(dataset.points,function(point){
+					if (point.hasValue()){
+						point.draw();
+					}
+				});
+
+			},this);
+
+		}
+
+	});
+
+
+
+
+
+}).call(this);
\ No newline at end of file
diff --git a/vendor/assets/javascripts/chart-lib.min.js b/vendor/assets/javascripts/chart-lib.min.js
deleted file mode 100644
index 3a0a2c87345f54eac4a8da8a9e1a81868c645ec7..0000000000000000000000000000000000000000
--- a/vendor/assets/javascripts/chart-lib.min.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/*!
- * Chart.js
- * http://chartjs.org/
- * Version: 1.0.2
- *
- * Copyright 2015 Nick Downie
- * Released under the MIT license
- * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
- */
-(function(){"use strict";var t=this,i=t.Chart,e=function(t){this.canvas=t.canvas,this.ctx=t;var i=function(t,i){return t["offset"+i]?t["offset"+i]:document.defaultView.getComputedStyle(t).getPropertyValue(i)},e=this.width=i(t.canvas,"Width"),n=this.height=i(t.canvas,"Height");t.canvas.width=e,t.canvas.height=n;var e=this.width=t.canvas.width,n=this.height=t.canvas.height;return this.aspectRatio=this.width/this.height,s.retinaScale(this),this};e.defaults={global:{animation:!0,animationSteps:60,animationEasing:"easeOutQuart",showScale:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleIntegersOnly:!0,scaleBeginAtZero:!1,scaleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",responsive:!1,maintainAspectRatio:!0,showTooltips:!0,customTooltips:!1,tooltipEvents:["mousemove","touchstart","touchmove","mouseout"],tooltipFillColor:"rgba(0,0,0,0.8)",tooltipFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipFontSize:14,tooltipFontStyle:"normal",tooltipFontColor:"#fff",tooltipTitleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipTitleFontSize:14,tooltipTitleFontStyle:"bold",tooltipTitleFontColor:"#fff",tooltipYPadding:6,tooltipXPadding:6,tooltipCaretSize:8,tooltipCornerRadius:6,tooltipXOffset:10,tooltipTemplate:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTooltipTemplate:"<%= value %>",multiTooltipKeyBackground:"#fff",onAnimationProgress:function(){},onAnimationComplete:function(){}}},e.types={};var s=e.helpers={},n=s.each=function(t,i,e){var s=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var n;for(n=0;n<t.length;n++)i.apply(e,[t[n],n].concat(s))}else for(var o in t)i.apply(e,[t[o],o].concat(s))},o=s.clone=function(t){var i={};return n(t,function(e,s){t.hasOwnProperty(s)&&(i[s]=e)}),i},a=s.extend=function(t){return n(Array.prototype.slice.call(arguments,1),function(i){n(i,function(e,s){i.hasOwnProperty(s)&&(t[s]=e)})}),t},h=s.merge=function(){var t=Array.prototype.slice.call(arguments,0);return t.unshift({}),a.apply(null,t)},l=s.indexOf=function(t,i){if(Array.prototype.indexOf)return t.indexOf(i);for(var e=0;e<t.length;e++)if(t[e]===i)return e;return-1},r=(s.where=function(t,i){var e=[];return s.each(t,function(t){i(t)&&e.push(t)}),e},s.findNextWhere=function(t,i,e){e||(e=-1);for(var s=e+1;s<t.length;s++){var n=t[s];if(i(n))return n}},s.findPreviousWhere=function(t,i,e){e||(e=t.length);for(var s=e-1;s>=0;s--){var n=t[s];if(i(n))return n}},s.inherits=function(t){var i=this,e=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return i.apply(this,arguments)},s=function(){this.constructor=e};return s.prototype=i.prototype,e.prototype=new s,e.extend=r,t&&a(e.prototype,t),e.__super__=i.prototype,e}),c=s.noop=function(){},u=s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),d=s.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},p=s.amd="function"==typeof define&&define.amd,f=s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},g=s.max=function(t){return Math.max.apply(Math,t)},m=s.min=function(t){return Math.min.apply(Math,t)},v=(s.cap=function(t,i,e){if(f(i)){if(t>i)return i}else if(f(e)&&e>t)return e;return t},s.getDecimalPlaces=function(t){return t%1!==0&&f(t)?t.toString().split(".")[1].length:0}),S=s.radians=function(t){return t*(Math.PI/180)},x=(s.getAngleFromPoint=function(t,i){var e=i.x-t.x,s=i.y-t.y,n=Math.sqrt(e*e+s*s),o=2*Math.PI+Math.atan2(s,e);return 0>e&&0>s&&(o+=2*Math.PI),{angle:o,distance:n}},s.aliasPixel=function(t){return t%2===0?0:.5}),y=(s.splineCurve=function(t,i,e,s){var n=Math.sqrt(Math.pow(i.x-t.x,2)+Math.pow(i.y-t.y,2)),o=Math.sqrt(Math.pow(e.x-i.x,2)+Math.pow(e.y-i.y,2)),a=s*n/(n+o),h=s*o/(n+o);return{inner:{x:i.x-a*(e.x-t.x),y:i.y-a*(e.y-t.y)},outer:{x:i.x+h*(e.x-t.x),y:i.y+h*(e.y-t.y)}}},s.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),C=(s.calculateScaleRange=function(t,i,e,s,n){var o=2,a=Math.floor(i/(1.5*e)),h=o>=a,l=g(t),r=m(t);l===r&&(l+=.5,r>=.5&&!s?r-=.5:l+=.5);for(var c=Math.abs(l-r),u=y(c),d=Math.ceil(l/(1*Math.pow(10,u)))*Math.pow(10,u),p=s?0:Math.floor(r/(1*Math.pow(10,u)))*Math.pow(10,u),f=d-p,v=Math.pow(10,u),S=Math.round(f/v);(S>a||a>2*S)&&!h;)if(S>a)v*=2,S=Math.round(f/v),S%1!==0&&(h=!0);else if(n&&u>=0){if(v/2%1!==0)break;v/=2,S=Math.round(f/v)}else v/=2,S=Math.round(f/v);return h&&(S=o,v=f/S),{steps:S,stepValue:v,min:p,max:p+S*v}},s.template=function(t,i){function e(t,i){var e=/\W/.test(t)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+t.replace(/[\r\t\n]/g," ").split("<%").join("	").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("	").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):s[t]=s[t];return i?e(i):e}if(t instanceof Function)return t(i);var s={};return e(t,i)}),w=(s.generateLabels=function(t,i,e,s){var o=new Array(i);return labelTemplateString&&n(o,function(i,n){o[n]=C(t,{value:e+s*(n+1)})}),o},s.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),-(s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)))},easeOutElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),s*Math.pow(2,-10*t)*Math.sin(2*(1*t-i)*Math.PI/e)+1)},easeInOutElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:2==(t/=.5)?1:(e||(e=.3*1.5),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),1>t?-.5*s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e):s*Math.pow(2,-10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)*.5+1)},easeInBack:function(t){var i=1.70158;return 1*(t/=1)*t*((i+1)*t-i)},easeOutBack:function(t){var i=1.70158;return 1*((t=t/1-1)*t*((i+1)*t+i)+1)},easeInOutBack:function(t){var i=1.70158;return(t/=.5)<1?.5*t*t*(((i*=1.525)+1)*t-i):.5*((t-=2)*t*(((i*=1.525)+1)*t+i)+2)},easeInBounce:function(t){return 1-w.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?7.5625*t*t:2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*w.easeInBounce(2*t):.5*w.easeOutBounce(2*t-1)+.5}}),b=s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),P=s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),L=(s.animationLoop=function(t,i,e,s,n,o){var a=0,h=w[e]||w.linear,l=function(){a++;var e=a/i,r=h(e);t.call(o,r,e,a),s.call(o,r,e),i>a?o.animationFrame=b(l):n.apply(o)};b(l)},s.getRelativePosition=function(t){var i,e,s=t.originalEvent||t,n=t.currentTarget||t.srcElement,o=n.getBoundingClientRect();return s.touches?(i=s.touches[0].clientX-o.left,e=s.touches[0].clientY-o.top):(i=s.clientX-o.left,e=s.clientY-o.top),{x:i,y:e}},s.addEvent=function(t,i,e){t.addEventListener?t.addEventListener(i,e):t.attachEvent?t.attachEvent("on"+i,e):t["on"+i]=e}),k=s.removeEvent=function(t,i,e){t.removeEventListener?t.removeEventListener(i,e,!1):t.detachEvent?t.detachEvent("on"+i,e):t["on"+i]=c},F=(s.bindEvents=function(t,i,e){t.events||(t.events={}),n(i,function(i){t.events[i]=function(){e.apply(t,arguments)},L(t.chart.canvas,i,t.events[i])})},s.unbindEvents=function(t,i){n(i,function(i,e){k(t.chart.canvas,e,i)})}),R=s.getMaximumWidth=function(t){var i=t.parentNode;return i.clientWidth},T=s.getMaximumHeight=function(t){var i=t.parentNode;return i.clientHeight},A=(s.getMaximumSize=s.getMaximumWidth,s.retinaScale=function(t){var i=t.ctx,e=t.canvas.width,s=t.canvas.height;window.devicePixelRatio&&(i.canvas.style.width=e+"px",i.canvas.style.height=s+"px",i.canvas.height=s*window.devicePixelRatio,i.canvas.width=e*window.devicePixelRatio,i.scale(window.devicePixelRatio,window.devicePixelRatio))}),M=s.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},W=s.fontString=function(t,i,e){return i+" "+t+"px "+e},z=s.longestText=function(t,i,e){t.font=i;var s=0;return n(e,function(i){var e=t.measureText(i).width;s=e>s?e:s}),s},B=s.drawRoundedRectangle=function(t,i,e,s,n,o){t.beginPath(),t.moveTo(i+o,e),t.lineTo(i+s-o,e),t.quadraticCurveTo(i+s,e,i+s,e+o),t.lineTo(i+s,e+n-o),t.quadraticCurveTo(i+s,e+n,i+s-o,e+n),t.lineTo(i+o,e+n),t.quadraticCurveTo(i,e+n,i,e+n-o),t.lineTo(i,e+o),t.quadraticCurveTo(i,e,i+o,e),t.closePath()};e.instances={},e.Type=function(t,i,s){this.options=i,this.chart=s,this.id=u(),e.instances[this.id]=this,i.responsive&&this.resize(),this.initialize.call(this,t)},a(e.Type.prototype,{initialize:function(){return this},clear:function(){return M(this.chart),this},stop:function(){return P(this.animationFrame),this},resize:function(t){this.stop();var i=this.chart.canvas,e=R(this.chart.canvas),s=this.options.maintainAspectRatio?e/this.chart.aspectRatio:T(this.chart.canvas);return i.width=this.chart.width=e,i.height=this.chart.height=s,A(this.chart),"function"==typeof t&&t.apply(this,Array.prototype.slice.call(arguments,1)),this},reflow:c,render:function(t){return t&&this.reflow(),this.options.animation&&!t?s.animationLoop(this.draw,this.options.animationSteps,this.options.animationEasing,this.options.onAnimationProgress,this.options.onAnimationComplete,this):(this.draw(),this.options.onAnimationComplete.call(this)),this},generateLegend:function(){return C(this.options.legendTemplate,this)},destroy:function(){this.clear(),F(this,this.events);var t=this.chart.canvas;t.width=this.chart.width,t.height=this.chart.height,t.style.removeProperty?(t.style.removeProperty("width"),t.style.removeProperty("height")):(t.style.removeAttribute("width"),t.style.removeAttribute("height")),delete e.instances[this.id]},showTooltip:function(t,i){"undefined"==typeof this.activeElements&&(this.activeElements=[]);var o=function(t){var i=!1;return t.length!==this.activeElements.length?i=!0:(n(t,function(t,e){t!==this.activeElements[e]&&(i=!0)},this),i)}.call(this,t);if(o||i){if(this.activeElements=t,this.draw(),this.options.customTooltips&&this.options.customTooltips(!1),t.length>0)if(this.datasets&&this.datasets.length>1){for(var a,h,r=this.datasets.length-1;r>=0&&(a=this.datasets[r].points||this.datasets[r].bars||this.datasets[r].segments,h=l(a,t[0]),-1===h);r--);var c=[],u=[],d=function(){var t,i,e,n,o,a=[],l=[],r=[];return s.each(this.datasets,function(i){t=i.points||i.bars||i.segments,t[h]&&t[h].hasValue()&&a.push(t[h])}),s.each(a,function(t){l.push(t.x),r.push(t.y),c.push(s.template(this.options.multiTooltipTemplate,t)),u.push({fill:t._saved.fillColor||t.fillColor,stroke:t._saved.strokeColor||t.strokeColor})},this),o=m(r),e=g(r),n=m(l),i=g(l),{x:n>this.chart.width/2?n:i,y:(o+e)/2}}.call(this,h);new e.MultiTooltip({x:d.x,y:d.y,xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,xOffset:this.options.tooltipXOffset,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,titleTextColor:this.options.tooltipTitleFontColor,titleFontFamily:this.options.tooltipTitleFontFamily,titleFontStyle:this.options.tooltipTitleFontStyle,titleFontSize:this.options.tooltipTitleFontSize,cornerRadius:this.options.tooltipCornerRadius,labels:c,legendColors:u,legendColorBackground:this.options.multiTooltipKeyBackground,title:t[0].label,chart:this.chart,ctx:this.chart.ctx,custom:this.options.customTooltips}).draw()}else n(t,function(t){var i=t.tooltipPosition();new e.Tooltip({x:Math.round(i.x),y:Math.round(i.y),xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,caretHeight:this.options.tooltipCaretSize,cornerRadius:this.options.tooltipCornerRadius,text:C(this.options.tooltipTemplate,t),chart:this.chart,custom:this.options.customTooltips}).draw()},this);return this}},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)}}),e.Type.extend=function(t){var i=this,s=function(){return i.apply(this,arguments)};if(s.prototype=o(i.prototype),a(s.prototype,t),s.extend=e.Type.extend,t.name||i.prototype.name){var n=t.name||i.prototype.name,l=e.defaults[i.prototype.name]?o(e.defaults[i.prototype.name]):{};e.defaults[n]=a(l,t.defaults),e.types[n]=s,e.prototype[n]=function(t,i){var o=h(e.defaults.global,e.defaults[n],i||{});return new s(t,o,this)}}else d("Name not provided for this chart, so it hasn't been registered");return i},e.Element=function(t){a(this,t),this.initialize.apply(this,arguments),this.save()},a(e.Element.prototype,{initialize:function(){},restore:function(t){return t?n(t,function(t){this[t]=this._saved[t]},this):a(this,this._saved),this},save:function(){return this._saved=o(this),delete this._saved._saved,this},update:function(t){return n(t,function(t,i){this._saved[i]=this[i],this[i]=t},this),this},transition:function(t,i){return n(t,function(t,e){this[e]=(t-this._saved[e])*i+this._saved[e]},this),this},tooltipPosition:function(){return{x:this.x,y:this.y}},hasValue:function(){return f(this.value)}}),e.Element.extend=r,e.Point=e.Element.extend({display:!0,inRange:function(t,i){var e=this.hitDetectionRadius+this.radius;return Math.pow(t-this.x,2)+Math.pow(i-this.y,2)<Math.pow(e,2)},draw:function(){if(this.display){var t=this.ctx;t.beginPath(),t.arc(this.x,this.y,this.radius,0,2*Math.PI),t.closePath(),t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.fillStyle=this.fillColor,t.fill(),t.stroke()}}}),e.Arc=e.Element.extend({inRange:function(t,i){var e=s.getAngleFromPoint(this,{x:t,y:i}),n=e.angle>=this.startAngle&&e.angle<=this.endAngle,o=e.distance>=this.innerRadius&&e.distance<=this.outerRadius;return n&&o},tooltipPosition:function(){var t=this.startAngle+(this.endAngle-this.startAngle)/2,i=(this.outerRadius-this.innerRadius)/2+this.innerRadius;return{x:this.x+Math.cos(t)*i,y:this.y+Math.sin(t)*i}},draw:function(t){var i=this.ctx;i.beginPath(),i.arc(this.x,this.y,this.outerRadius,this.startAngle,this.endAngle),i.arc(this.x,this.y,this.innerRadius,this.endAngle,this.startAngle,!0),i.closePath(),i.strokeStyle=this.strokeColor,i.lineWidth=this.strokeWidth,i.fillStyle=this.fillColor,i.fill(),i.lineJoin="bevel",this.showStroke&&i.stroke()}}),e.Rectangle=e.Element.extend({draw:function(){var t=this.ctx,i=this.width/2,e=this.x-i,s=this.x+i,n=this.base-(this.base-this.y),o=this.strokeWidth/2;this.showStroke&&(e+=o,s-=o,n+=o),t.beginPath(),t.fillStyle=this.fillColor,t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.moveTo(e,this.base),t.lineTo(e,n),t.lineTo(s,n),t.lineTo(s,this.base),t.fill(),this.showStroke&&t.stroke()},height:function(){return this.base-this.y},inRange:function(t,i){return t>=this.x-this.width/2&&t<=this.x+this.width/2&&i>=this.y&&i<=this.base}}),e.Tooltip=e.Element.extend({draw:function(){var t=this.chart.ctx;t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.xAlign="center",this.yAlign="above";var i=this.caretPadding=2,e=t.measureText(this.text).width+2*this.xPadding,s=this.fontSize+2*this.yPadding,n=s+this.caretHeight+i;this.x+e/2>this.chart.width?this.xAlign="left":this.x-e/2<0&&(this.xAlign="right"),this.y-n<0&&(this.yAlign="below");var o=this.x-e/2,a=this.y-n;if(t.fillStyle=this.fillColor,this.custom)this.custom(this);else{switch(this.yAlign){case"above":t.beginPath(),t.moveTo(this.x,this.y-i),t.lineTo(this.x+this.caretHeight,this.y-(i+this.caretHeight)),t.lineTo(this.x-this.caretHeight,this.y-(i+this.caretHeight)),t.closePath(),t.fill();break;case"below":a=this.y+i+this.caretHeight,t.beginPath(),t.moveTo(this.x,this.y+i),t.lineTo(this.x+this.caretHeight,this.y+i+this.caretHeight),t.lineTo(this.x-this.caretHeight,this.y+i+this.caretHeight),t.closePath(),t.fill()}switch(this.xAlign){case"left":o=this.x-e+(this.cornerRadius+this.caretHeight);break;case"right":o=this.x-(this.cornerRadius+this.caretHeight)}B(t,o,a,e,s,this.cornerRadius),t.fill(),t.fillStyle=this.textColor,t.textAlign="center",t.textBaseline="middle",t.fillText(this.text,o+e/2,a+s/2)}}}),e.MultiTooltip=e.Element.extend({initialize:function(){this.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.titleFont=W(this.titleFontSize,this.titleFontStyle,this.titleFontFamily),this.height=this.labels.length*this.fontSize+(this.labels.length-1)*(this.fontSize/2)+2*this.yPadding+1.5*this.titleFontSize,this.ctx.font=this.titleFont;var t=this.ctx.measureText(this.title).width,i=z(this.ctx,this.font,this.labels)+this.fontSize+3,e=g([i,t]);this.width=e+2*this.xPadding;var s=this.height/2;this.y-s<0?this.y=s:this.y+s>this.chart.height&&(this.y=this.chart.height-s),this.x>this.chart.width/2?this.x-=this.xOffset+this.width:this.x+=this.xOffset},getLineHeight:function(t){var i=this.y-this.height/2+this.yPadding,e=t-1;return 0===t?i+this.titleFontSize/2:i+(1.5*this.fontSize*e+this.fontSize/2)+1.5*this.titleFontSize},draw:function(){if(this.custom)this.custom(this);else{B(this.ctx,this.x,this.y-this.height/2,this.width,this.height,this.cornerRadius);var t=this.ctx;t.fillStyle=this.fillColor,t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=this.titleTextColor,t.font=this.titleFont,t.fillText(this.title,this.x+this.xPadding,this.getLineHeight(0)),t.font=this.font,s.each(this.labels,function(i,e){t.fillStyle=this.textColor,t.fillText(i,this.x+this.xPadding+this.fontSize+3,this.getLineHeight(e+1)),t.fillStyle=this.legendColorBackground,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize),t.fillStyle=this.legendColors[e].fill,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize)},this)}}}),e.Scale=e.Element.extend({initialize:function(){this.fit()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}));this.yLabelWidth=this.display&&this.showLabels?z(this.ctx,this.font,this.yLabels):0},addXLabel:function(t){this.xLabels.push(t),this.valuesCount++,this.fit()},removeXLabel:function(){this.xLabels.shift(),this.valuesCount--,this.fit()},fit:function(){this.startPoint=this.display?this.fontSize:0,this.endPoint=this.display?this.height-1.5*this.fontSize-5:this.height,this.startPoint+=this.padding,this.endPoint-=this.padding;var t,i=this.endPoint-this.startPoint;for(this.calculateYRange(i),this.buildYLabels(),this.calculateXLabelRotation();i>this.endPoint-this.startPoint;)i=this.endPoint-this.startPoint,t=this.yLabelWidth,this.calculateYRange(i),this.buildYLabels(),t<this.yLabelWidth&&this.calculateXLabelRotation()},calculateXLabelRotation:function(){this.ctx.font=this.font;var t,i,e=this.ctx.measureText(this.xLabels[0]).width,s=this.ctx.measureText(this.xLabels[this.xLabels.length-1]).width;if(this.xScalePaddingRight=s/2+3,this.xScalePaddingLeft=e/2>this.yLabelWidth+10?e/2:this.yLabelWidth+10,this.xLabelRotation=0,this.display){var n,o=z(this.ctx,this.font,this.xLabels);this.xLabelWidth=o;for(var a=Math.floor(this.calculateX(1)-this.calculateX(0))-6;this.xLabelWidth>a&&0===this.xLabelRotation||this.xLabelWidth>a&&this.xLabelRotation<=90&&this.xLabelRotation>0;)n=Math.cos(S(this.xLabelRotation)),t=n*e,i=n*s,t+this.fontSize/2>this.yLabelWidth+8&&(this.xScalePaddingLeft=t+this.fontSize/2),this.xScalePaddingRight=this.fontSize/2,this.xLabelRotation++,this.xLabelWidth=n*o;this.xLabelRotation>0&&(this.endPoint-=Math.sin(S(this.xLabelRotation))*o+3)}else this.xLabelWidth=0,this.xScalePaddingRight=this.padding,this.xScalePaddingLeft=this.padding},calculateYRange:c,drawingArea:function(){return this.startPoint-this.endPoint},calculateY:function(t){var i=this.drawingArea()/(this.min-this.max);return this.endPoint-i*(t-this.min)},calculateX:function(t){var i=(this.xLabelRotation>0,this.width-(this.xScalePaddingLeft+this.xScalePaddingRight)),e=i/Math.max(this.valuesCount-(this.offsetGridLines?0:1),1),s=e*t+this.xScalePaddingLeft;return this.offsetGridLines&&(s+=e/2),Math.round(s)},update:function(t){s.extend(this,t),this.fit()},draw:function(){var t=this.ctx,i=(this.endPoint-this.startPoint)/this.steps,e=Math.round(this.xScalePaddingLeft);this.display&&(t.fillStyle=this.textColor,t.font=this.font,n(this.yLabels,function(n,o){var a=this.endPoint-i*o,h=Math.round(a),l=this.showHorizontalLines;t.textAlign="right",t.textBaseline="middle",this.showLabels&&t.fillText(n,e-10,a),0!==o||l||(l=!0),l&&t.beginPath(),o>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),h+=s.aliasPixel(t.lineWidth),l&&(t.moveTo(e,h),t.lineTo(this.width,h),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(e-5,h),t.lineTo(e,h),t.stroke(),t.closePath()},this),n(this.xLabels,function(i,e){var s=this.calculateX(e)+x(this.lineWidth),n=this.calculateX(e-(this.offsetGridLines?.5:0))+x(this.lineWidth),o=this.xLabelRotation>0,a=this.showVerticalLines;0!==e||a||(a=!0),a&&t.beginPath(),e>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),a&&(t.moveTo(n,this.endPoint),t.lineTo(n,this.startPoint-3),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(n,this.endPoint),t.lineTo(n,this.endPoint+5),t.stroke(),t.closePath(),t.save(),t.translate(s,o?this.endPoint+12:this.endPoint+8),t.rotate(-1*S(this.xLabelRotation)),t.font=this.font,t.textAlign=o?"right":"center",t.textBaseline=o?"middle":"top",t.fillText(i,0,0),t.restore()},this))}}),e.RadialScale=e.Element.extend({initialize:function(){this.size=m([this.height,this.width]),this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2},calculateCenterOffset:function(t){var i=this.drawingArea/(this.max-this.min);return(t-this.min)*i},update:function(){this.lineArc?this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}))},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,i,e,s,n,o,a,h,l,r,c,u,d=m([this.height/2-this.pointLabelFontSize-5,this.width/2]),p=this.width,g=0;for(this.ctx.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),i=0;i<this.valuesCount;i++)t=this.getPointPosition(i,d),e=this.ctx.measureText(C(this.templateString,{value:this.labels[i]})).width+5,0===i||i===this.valuesCount/2?(s=e/2,t.x+s>p&&(p=t.x+s,n=i),t.x-s<g&&(g=t.x-s,a=i)):i<this.valuesCount/2?t.x+e>p&&(p=t.x+e,n=i):i>this.valuesCount/2&&t.x-e<g&&(g=t.x-e,a=i);l=g,r=Math.ceil(p-this.width),o=this.getIndexAngle(n),h=this.getIndexAngle(a),c=r/Math.sin(o+Math.PI/2),u=l/Math.sin(h+Math.PI/2),c=f(c)?c:0,u=f(u)?u:0,this.drawingArea=d-(u+c)/2,this.setCenterPoint(u,c)},setCenterPoint:function(t,i){var e=this.width-i-this.drawingArea,s=t+this.drawingArea;this.xCenter=(s+e)/2,this.yCenter=this.height/2},getIndexAngle:function(t){var i=2*Math.PI/this.valuesCount;return t*i-Math.PI/2},getPointPosition:function(t,i){var e=this.getIndexAngle(t);return{x:Math.cos(e)*i+this.xCenter,y:Math.sin(e)*i+this.yCenter}},draw:function(){if(this.display){var t=this.ctx;if(n(this.yLabels,function(i,e){if(e>0){var s,n=e*(this.drawingArea/this.steps),o=this.yCenter-n;if(this.lineWidth>0)if(t.strokeStyle=this.lineColor,t.lineWidth=this.lineWidth,this.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,n,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var a=0;a<this.valuesCount;a++)s=this.getPointPosition(a,this.calculateCenterOffset(this.min+e*this.stepValue)),0===a?t.moveTo(s.x,s.y):t.lineTo(s.x,s.y);t.closePath(),t.stroke()}if(this.showLabels){if(t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.showLabelBackdrop){var h=t.measureText(i).width;t.fillStyle=this.backdropColor,t.fillRect(this.xCenter-h/2-this.backdropPaddingX,o-this.fontSize/2-this.backdropPaddingY,h+2*this.backdropPaddingX,this.fontSize+2*this.backdropPaddingY)}t.textAlign="center",t.textBaseline="middle",t.fillStyle=this.fontColor,t.fillText(i,this.xCenter,o)}}},this),!this.lineArc){t.lineWidth=this.angleLineWidth,t.strokeStyle=this.angleLineColor;for(var i=this.valuesCount-1;i>=0;i--){if(this.angleLineWidth>0){var e=this.getPointPosition(i,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(e.x,e.y),t.stroke(),t.closePath()}var s=this.getPointPosition(i,this.calculateCenterOffset(this.max)+5);t.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),t.fillStyle=this.pointLabelFontColor;var o=this.labels.length,a=this.labels.length/2,h=a/2,l=h>i||i>o-h,r=i===h||i===o-h;t.textAlign=0===i?"center":i===a?"center":a>i?"left":"right",t.textBaseline=r?"middle":l?"bottom":"top",t.fillText(this.labels[i],s.x,s.y)}}}}}),s.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){n(e.instances,function(t){t.options.responsive&&t.resize(t.render,!0)})},50)}}()),p?define(function(){return e}):"object"==typeof module&&module.exports&&(module.exports=e),t.Chart=e,e.noConflict=function(){return t.Chart=i,e}}).call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleBeginAtZero:!0,scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].fillColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Bar",defaults:s,initialize:function(t){var s=this.options;this.ScaleClass=i.Scale.extend({offsetGridLines:!0,calculateBarX:function(t,i,e){var n=this.calculateBaseWidth(),o=this.calculateX(e)-n/2,a=this.calculateBarWidth(t);return o+a*i+i*s.barDatasetSpacing+a/2},calculateBaseWidth:function(){return this.calculateX(1)-this.calculateX(0)-2*s.barValueSpacing},calculateBarWidth:function(t){var i=this.calculateBaseWidth()-(t-1)*s.barDatasetSpacing;return i/t}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getBarsAtEvent(t):[];this.eachBars(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),this.BarClass=i.Rectangle.extend({strokeWidth:this.options.barStrokeWidth,showStroke:this.options.barShowStroke,ctx:this.chart.ctx}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,bars:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.bars.push(new this.BarClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.strokeColor,fillColor:i.fillColor,highlightFill:i.highlightFill||i.fillColor,highlightStroke:i.highlightStroke||i.strokeColor}))},this)},this),this.buildScale(t.labels),this.BarClass.prototype.base=this.scale.endPoint,this.eachBars(function(t,i,s){e.extend(t,{width:this.scale.calculateBarWidth(this.datasets.length),x:this.scale.calculateBarX(this.datasets.length,s,i),y:this.scale.endPoint}),t.save()},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachBars(function(t){t.save()}),this.render()},eachBars:function(t){e.each(this.datasets,function(i,s){e.each(i.bars,t,this,s)},this)},getBarsAtEvent:function(t){for(var i,s=[],n=e.getRelativePosition(t),o=function(t){s.push(t.bars[i])},a=0;a<this.datasets.length;a++)for(i=0;i<this.datasets[a].bars.length;i++)if(this.datasets[a].bars[i].inRange(n.x,n.y))return e.each(this.datasets,o),s;return s},buildScale:function(t){var i=this,s=function(){var t=[];return i.eachBars(function(i){t.push(i.value)}),t},n={templateString:this.options.scaleLabel,height:this.chart.height,width:this.chart.width,ctx:this.chart.ctx,textColor:this.options.scaleFontColor,fontSize:this.options.scaleFontSize,fontStyle:this.options.scaleFontStyle,fontFamily:this.options.scaleFontFamily,valuesCount:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var i=e.calculateScaleRange(s(),t,this.fontSize,this.beginAtZero,this.integersOnly);e.extend(this,i)},xLabels:t,font:e.fontString(this.options.scaleFontSize,this.options.scaleFontStyle,this.options.scaleFontFamily),lineWidth:this.options.scaleLineWidth,lineColor:this.options.scaleLineColor,showHorizontalLines:this.options.scaleShowHorizontalLines,showVerticalLines:this.options.scaleShowVerticalLines,gridLineWidth:this.options.scaleShowGridLines?this.options.scaleGridLineWidth:0,gridLineColor:this.options.scaleShowGridLines?this.options.scaleGridLineColor:"rgba(0,0,0,0)",padding:this.options.showScale?0:this.options.barShowStroke?this.options.barStrokeWidth:0,showLabels:this.options.scaleShowLabels,display:this.options.showScale};this.options.scaleOverride&&e.extend(n,{calculateYRange:e.noop,steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}),this.scale=new this.ScaleClass(n)},addData:function(t,i){e.each(t,function(t,e){this.datasets[e].bars.push(new this.BarClass({value:t,label:i,x:this.scale.calculateBarX(this.datasets.length,e,this.scale.valuesCount+1),y:this.scale.endPoint,width:this.scale.calculateBarWidth(this.datasets.length),base:this.scale.endPoint,strokeColor:this.datasets[e].strokeColor,fillColor:this.datasets[e].fillColor}))
-},this),this.scale.addXLabel(i),this.update()},removeData:function(){this.scale.removeXLabel(),e.each(this.datasets,function(t){t.bars.shift()},this),this.update()},reflow:function(){e.extend(this.BarClass.prototype,{y:this.scale.endPoint,base:this.scale.endPoint});var t=e.extend({height:this.chart.height,width:this.chart.width});this.scale.update(t)},draw:function(t){var i=t||1;this.clear();this.chart.ctx;this.scale.draw(i),e.each(this.datasets,function(t,s){e.each(t.bars,function(t,e){t.hasValue()&&(t.base=this.scale.endPoint,t.transition({x:this.scale.calculateBarX(this.datasets.length,s,e),y:this.scale.calculateY(t.value),width:this.scale.calculateBarWidth(this.datasets.length)},i).draw())},this)},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Doughnut",defaults:s,initialize:function(t){this.segments=[],this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,this.SegmentArc=i.Arc.extend({ctx:this.chart.ctx,x:this.chart.width/2,y:this.chart.height/2}),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.calculateTotal(t),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({value:t.value,outerRadius:this.options.animateScale?0:this.outerRadius,innerRadius:this.options.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout,fillColor:t.color,highlightColor:t.highlight||t.color,showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,startAngle:1.5*Math.PI,circumference:this.options.animateRotate?0:this.calculateCircumference(t.value),label:t.label})),e||(this.reflow(),this.update())},calculateCircumference:function(t){return 2*Math.PI*(Math.abs(t)/this.total)},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=Math.abs(t.value)},this)},update:function(){this.calculateTotal(this.segments),e.each(this.activeElements,function(t){t.restore(["fillColor"])}),e.each(this.segments,function(t){t.save()}),this.render()},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,e.each(this.segments,function(t){t.update({outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout})},this)},draw:function(t){var i=t?t:1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.calculateCircumference(t.value),outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout},i),t.endAngle=t.startAngle+t.circumference,t.draw(),0===e&&(t.startAngle=1.5*Math.PI),e<this.segments.length-1&&(this.segments[e+1].startAngle=t.endAngle)},this)}}),i.types.Doughnut.extend({name:"Pie",defaults:e.merge(s,{percentageInnerCutout:0})})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,bezierCurve:!0,bezierCurveTension:.4,pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:1,pointHitDetectionRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].strokeColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Line",defaults:s,initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx,inRange:function(t){return Math.pow(t-this.x,2)<Math.pow(this.radius+this.hitDetectionRadius,2)}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this),this.buildScale(t.labels),this.eachPoints(function(t,i){e.extend(t,{x:this.scale.calculateX(i),y:this.scale.endPoint}),t.save()},this)},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachPoints(function(t){t.save()}),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.datasets,function(t){e.each(t.points,function(t){t.inRange(s.x,s.y)&&i.push(t)})},this),i},buildScale:function(t){var s=this,n=function(){var t=[];return s.eachPoints(function(i){t.push(i.value)}),t},o={templateString:this.options.scaleLabel,height:this.chart.height,width:this.chart.width,ctx:this.chart.ctx,textColor:this.options.scaleFontColor,fontSize:this.options.scaleFontSize,fontStyle:this.options.scaleFontStyle,fontFamily:this.options.scaleFontFamily,valuesCount:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var i=e.calculateScaleRange(n(),t,this.fontSize,this.beginAtZero,this.integersOnly);e.extend(this,i)},xLabels:t,font:e.fontString(this.options.scaleFontSize,this.options.scaleFontStyle,this.options.scaleFontFamily),lineWidth:this.options.scaleLineWidth,lineColor:this.options.scaleLineColor,showHorizontalLines:this.options.scaleShowHorizontalLines,showVerticalLines:this.options.scaleShowVerticalLines,gridLineWidth:this.options.scaleShowGridLines?this.options.scaleGridLineWidth:0,gridLineColor:this.options.scaleShowGridLines?this.options.scaleGridLineColor:"rgba(0,0,0,0)",padding:this.options.showScale?0:this.options.pointDotRadius+this.options.pointDotStrokeWidth,showLabels:this.options.scaleShowLabels,display:this.options.showScale};this.options.scaleOverride&&e.extend(o,{calculateYRange:e.noop,steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}),this.scale=new i.Scale(o)},addData:function(t,i){e.each(t,function(t,e){this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:this.scale.calculateX(this.scale.valuesCount+1),y:this.scale.endPoint,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.addXLabel(i),this.update()},removeData:function(){this.scale.removeXLabel(),e.each(this.datasets,function(t){t.points.shift()},this),this.update()},reflow:function(){var t=e.extend({height:this.chart.height,width:this.chart.width});this.scale.update(t)},draw:function(t){var i=t||1;this.clear();var s=this.chart.ctx,n=function(t){return null!==t.value},o=function(t,i,s){return e.findNextWhere(i,n,s)||t},a=function(t,i,s){return e.findPreviousWhere(i,n,s)||t};this.scale.draw(i),e.each(this.datasets,function(t){var h=e.where(t.points,n);e.each(t.points,function(t,e){t.hasValue()&&t.transition({y:this.scale.calculateY(t.value),x:this.scale.calculateX(e)},i)},this),this.options.bezierCurve&&e.each(h,function(t,i){var s=i>0&&i<h.length-1?this.options.bezierCurveTension:0;t.controlPoints=e.splineCurve(a(t,h,i),t,o(t,h,i),s),t.controlPoints.outer.y>this.scale.endPoint?t.controlPoints.outer.y=this.scale.endPoint:t.controlPoints.outer.y<this.scale.startPoint&&(t.controlPoints.outer.y=this.scale.startPoint),t.controlPoints.inner.y>this.scale.endPoint?t.controlPoints.inner.y=this.scale.endPoint:t.controlPoints.inner.y<this.scale.startPoint&&(t.controlPoints.inner.y=this.scale.startPoint)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(h,function(t,i){if(0===i)s.moveTo(t.x,t.y);else if(this.options.bezierCurve){var e=a(t,h,i);s.bezierCurveTo(e.controlPoints.outer.x,e.controlPoints.outer.y,t.controlPoints.inner.x,t.controlPoints.inner.y,t.x,t.y)}else s.lineTo(t.x,t.y)},this),s.stroke(),this.options.datasetFill&&h.length>0&&(s.lineTo(h[h.length-1].x,this.scale.endPoint),s.lineTo(h[0].x,this.scale.endPoint),s.fillStyle=t.fillColor,s.closePath(),s.fill()),e.each(h,function(t){t.draw()})},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBeginAtZero:!0,scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,scaleShowLine:!0,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"PolarArea",defaults:s,initialize:function(t){this.segments=[],this.SegmentArc=i.Arc.extend({showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,ctx:this.chart.ctx,innerRadius:0,x:this.chart.width/2,y:this.chart.height/2}),this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,lineArc:!0,width:this.chart.width,height:this.chart.height,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,valuesCount:t.length}),this.updateScaleRange(t),this.scale.update(),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value,outerRadius:this.options.animateScale?0:this.scale.calculateCenterOffset(t.value),circumference:this.options.animateRotate?0:this.scale.getCircumference(),startAngle:1.5*Math.PI})),e||(this.reflow(),this.update())},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=t.value},this),this.scale.valuesCount=this.segments.length},updateScaleRange:function(t){var i=[];e.each(t,function(t){i.push(t.value)});var s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s,{size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},update:function(){this.calculateTotal(this.segments),e.each(this.segments,function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.updateScaleRange(this.segments),this.scale.update(),e.extend(this.scale,{xCenter:this.chart.width/2,yCenter:this.chart.height/2}),e.each(this.segments,function(t){t.update({outerRadius:this.scale.calculateCenterOffset(t.value)})},this)},draw:function(t){var i=t||1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.scale.getCircumference(),outerRadius:this.scale.calculateCenterOffset(t.value)},i),t.endAngle=t.startAngle+t.circumference,0===e&&(t.startAngle=1.5*Math.PI),e<this.segments.length-1&&(this.segments[e+1].startAngle=t.endAngle),t.draw()},this),this.scale.draw()}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers;i.Type.extend({name:"Radar",defaults:{scaleShowLine:!0,angleShowLineOut:!0,scaleShowLabels:!1,scaleBeginAtZero:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:10,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,pointHitDetectionRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].strokeColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'},initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx}),this.datasets=[],this.buildScale(t),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){var o;this.scale.animation||(o=this.scale.getPointPosition(n,this.scale.calculateCenterOffset(e))),s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,x:this.options.animation?this.scale.xCenter:o.x,y:this.options.animation?this.scale.yCenter:o.y,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this)},this),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=e.getRelativePosition(t),s=e.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},i),n=2*Math.PI/this.scale.valuesCount,o=Math.round((s.angle-1.5*Math.PI)/n),a=[];return(o>=this.scale.valuesCount||0>o)&&(o=0),s.distance<=this.scale.drawingArea&&e.each(this.datasets,function(t){a.push(t.points[o])}),a},buildScale:function(t){this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,angleLineColor:this.options.angleLineColor,angleLineWidth:this.options.angleShowLineOut?this.options.angleLineWidth:0,pointLabelFontColor:this.options.pointLabelFontColor,pointLabelFontSize:this.options.pointLabelFontSize,pointLabelFontFamily:this.options.pointLabelFontFamily,pointLabelFontStyle:this.options.pointLabelFontStyle,height:this.chart.height,width:this.chart.width,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,labels:t.labels,valuesCount:t.datasets[0].data.length}),this.scale.setScaleSize(),this.updateScaleRange(t.datasets),this.scale.buildYLabels()},updateScaleRange:function(t){var i=function(){var i=[];return e.each(t,function(t){t.data?i=i.concat(t.data):e.each(t.points,function(t){i.push(t.value)})}),i}(),s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s)},addData:function(t,i){this.scale.valuesCount++,e.each(t,function(t,e){var s=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:s.x,y:s.y,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.labels.push(i),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),e.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2}),this.updateScaleRange(this.datasets),this.scale.setScaleSize(),this.scale.buildYLabels()},draw:function(t){var i=t||1,s=this.chart.ctx;this.clear(),this.scale.draw(),e.each(this.datasets,function(t){e.each(t.points,function(t,e){t.hasValue()&&t.transition(this.scale.getPointPosition(e,this.scale.calculateCenterOffset(t.value)),i)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(t.points,function(t,i){0===i?s.moveTo(t.x,t.y):s.lineTo(t.x,t.y)},this),s.closePath(),s.stroke(),s.fillStyle=t.fillColor,s.fill(),e.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this);
\ No newline at end of file
diff --git a/vendor/assets/javascripts/fuzzaldrin-plus.js b/vendor/assets/javascripts/fuzzaldrin-plus.js
new file mode 100644
index 0000000000000000000000000000000000000000..1985e3f8f6ccb8e3f764d8bad7bec52610c7d139
--- /dev/null
+++ b/vendor/assets/javascripts/fuzzaldrin-plus.js
@@ -0,0 +1,1161 @@
+/*!
+ * fuzzaldrin-plus.js - 0.3.1
+ * https://github.com/jeancroy/fuzzaldrin-plus
+ *
+ * Copyright 2016 - Jean Christophe Roy
+ * Released under the MIT license
+ * https://github.com/jeancroy/fuzzaldrin-plus/raw/master/LICENSE.md
+ */
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+fuzzaldrinPlus = require('fuzzaldrin-plus');
+
+},{"fuzzaldrin-plus":3}],2:[function(require,module,exports){
+(function() {
+  var PathSeparator, legacy_scorer, pluckCandidates, scorer, sortCandidates;
+
+  scorer = require('./scorer');
+
+  legacy_scorer = require('./legacy');
+
+  pluckCandidates = function(a) {
+    return a.candidate;
+  };
+
+  sortCandidates = function(a, b) {
+    return b.score - a.score;
+  };
+
+  PathSeparator = require('path').sep;
+
+  module.exports = function(candidates, query, _arg) {
+    var allowErrors, bAllowErrors, bKey, candidate, coreQuery, key, legacy, maxInners, maxResults, prepQuery, queryHasSlashes, score, scoredCandidates, spotLeft, string, _i, _j, _len, _len1, _ref;
+    _ref = _arg != null ? _arg : {}, key = _ref.key, maxResults = _ref.maxResults, maxInners = _ref.maxInners, allowErrors = _ref.allowErrors, legacy = _ref.legacy;
+    scoredCandidates = [];
+    spotLeft = (maxInners != null) && maxInners > 0 ? maxInners : candidates.length;
+    bAllowErrors = !!allowErrors;
+    bKey = key != null;
+    prepQuery = scorer.prepQuery(query);
+    if (!legacy) {
+      for (_i = 0, _len = candidates.length; _i < _len; _i++) {
+        candidate = candidates[_i];
+        string = bKey ? candidate[key] : candidate;
+        if (!string) {
+          continue;
+        }
+        score = scorer.score(string, query, prepQuery, bAllowErrors);
+        if (score > 0) {
+          scoredCandidates.push({
+            candidate: candidate,
+            score: score
+          });
+          if (!--spotLeft) {
+            break;
+          }
+        }
+      }
+    } else {
+      queryHasSlashes = prepQuery.depth > 0;
+      coreQuery = prepQuery.core;
+      for (_j = 0, _len1 = candidates.length; _j < _len1; _j++) {
+        candidate = candidates[_j];
+        string = key != null ? candidate[key] : candidate;
+        if (!string) {
+          continue;
+        }
+        score = legacy_scorer.score(string, coreQuery, queryHasSlashes);
+        if (!queryHasSlashes) {
+          score = legacy_scorer.basenameScore(string, coreQuery, score);
+        }
+        if (score > 0) {
+          scoredCandidates.push({
+            candidate: candidate,
+            score: score
+          });
+        }
+      }
+    }
+    scoredCandidates.sort(sortCandidates);
+    candidates = scoredCandidates.map(pluckCandidates);
+    if (maxResults != null) {
+      candidates = candidates.slice(0, maxResults);
+    }
+    return candidates;
+  };
+
+}).call(this);
+
+},{"./legacy":4,"./scorer":6,"path":7}],3:[function(require,module,exports){
+(function() {
+  var PathSeparator, filter, legacy_scorer, matcher, prepQueryCache, scorer;
+
+  scorer = require('./scorer');
+
+  legacy_scorer = require('./legacy');
+
+  filter = require('./filter');
+
+  matcher = require('./matcher');
+
+  PathSeparator = require('path').sep;
+
+  prepQueryCache = null;
+
+  module.exports = {
+    filter: function(candidates, query, options) {
+      if (!((query != null ? query.length : void 0) && (candidates != null ? candidates.length : void 0))) {
+        return [];
+      }
+      return filter(candidates, query, options);
+    },
+    prepQuery: function(query) {
+      return scorer.prepQuery(query);
+    },
+    score: function(string, query, prepQuery, _arg) {
+      var allowErrors, coreQuery, legacy, queryHasSlashes, score, _ref;
+      _ref = _arg != null ? _arg : {}, allowErrors = _ref.allowErrors, legacy = _ref.legacy;
+      if (!((string != null ? string.length : void 0) && (query != null ? query.length : void 0))) {
+        return 0;
+      }
+      if (prepQuery == null) {
+        prepQuery = prepQueryCache && prepQueryCache.query === query ? prepQueryCache : (prepQueryCache = scorer.prepQuery(query));
+      }
+      if (!legacy) {
+        score = scorer.score(string, query, prepQuery, !!allowErrors);
+      } else {
+        queryHasSlashes = prepQuery.depth > 0;
+        coreQuery = prepQuery.core;
+        score = legacy_scorer.score(string, coreQuery, queryHasSlashes);
+        if (!queryHasSlashes) {
+          score = legacy_scorer.basenameScore(string, coreQuery, score);
+        }
+      }
+      return score;
+    },
+    match: function(string, query, prepQuery, _arg) {
+      var allowErrors, baseMatches, matches, query_lw, string_lw, _i, _ref, _results;
+      allowErrors = (_arg != null ? _arg : {}).allowErrors;
+      if (!string) {
+        return [];
+      }
+      if (!query) {
+        return [];
+      }
+      if (string === query) {
+        return (function() {
+          _results = [];
+          for (var _i = 0, _ref = string.length; 0 <= _ref ? _i < _ref : _i > _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); }
+          return _results;
+        }).apply(this);
+      }
+      if (prepQuery == null) {
+        prepQuery = prepQueryCache && prepQueryCache.query === query ? prepQueryCache : (prepQueryCache = scorer.prepQuery(query));
+      }
+      if (!(allowErrors || scorer.isMatch(string, prepQuery.core_lw, prepQuery.core_up))) {
+        return [];
+      }
+      string_lw = string.toLowerCase();
+      query_lw = prepQuery.query_lw;
+      matches = matcher.match(string, string_lw, prepQuery);
+      if (matches.length === 0) {
+        return matches;
+      }
+      if (string.indexOf(PathSeparator) > -1) {
+        baseMatches = matcher.basenameMatch(string, string_lw, prepQuery);
+        matches = matcher.mergeMatches(matches, baseMatches);
+      }
+      return matches;
+    }
+  };
+
+}).call(this);
+
+},{"./filter":2,"./legacy":4,"./matcher":5,"./scorer":6,"path":7}],4:[function(require,module,exports){
+(function() {
+  var PathSeparator, queryIsLastPathSegment;
+
+  PathSeparator = require('path').sep;
+
+  exports.basenameScore = function(string, query, score) {
+    var base, depth, index, lastCharacter, segmentCount, slashCount;
+    index = string.length - 1;
+    while (string[index] === PathSeparator) {
+      index--;
+    }
+    slashCount = 0;
+    lastCharacter = index;
+    base = null;
+    while (index >= 0) {
+      if (string[index] === PathSeparator) {
+        slashCount++;
+        if (base == null) {
+          base = string.substring(index + 1, lastCharacter + 1);
+        }
+      } else if (index === 0) {
+        if (lastCharacter < string.length - 1) {
+          if (base == null) {
+            base = string.substring(0, lastCharacter + 1);
+          }
+        } else {
+          if (base == null) {
+            base = string;
+          }
+        }
+      }
+      index--;
+    }
+    if (base === string) {
+      score *= 2;
+    } else if (base) {
+      score += exports.score(base, query);
+    }
+    segmentCount = slashCount + 1;
+    depth = Math.max(1, 10 - segmentCount);
+    score *= depth * 0.01;
+    return score;
+  };
+
+  exports.score = function(string, query) {
+    var character, characterScore, indexInQuery, indexInString, lowerCaseIndex, minIndex, queryLength, queryScore, stringLength, totalCharacterScore, upperCaseIndex, _ref;
+    if (string === query) {
+      return 1;
+    }
+    if (queryIsLastPathSegment(string, query)) {
+      return 1;
+    }
+    totalCharacterScore = 0;
+    queryLength = query.length;
+    stringLength = string.length;
+    indexInQuery = 0;
+    indexInString = 0;
+    while (indexInQuery < queryLength) {
+      character = query[indexInQuery++];
+      lowerCaseIndex = string.indexOf(character.toLowerCase());
+      upperCaseIndex = string.indexOf(character.toUpperCase());
+      minIndex = Math.min(lowerCaseIndex, upperCaseIndex);
+      if (minIndex === -1) {
+        minIndex = Math.max(lowerCaseIndex, upperCaseIndex);
+      }
+      indexInString = minIndex;
+      if (indexInString === -1) {
+        return 0;
+      }
+      characterScore = 0.1;
+      if (string[indexInString] === character) {
+        characterScore += 0.1;
+      }
+      if (indexInString === 0 || string[indexInString - 1] === PathSeparator) {
+        characterScore += 0.8;
+      } else if ((_ref = string[indexInString - 1]) === '-' || _ref === '_' || _ref === ' ') {
+        characterScore += 0.7;
+      }
+      string = string.substring(indexInString + 1, stringLength);
+      totalCharacterScore += characterScore;
+    }
+    queryScore = totalCharacterScore / queryLength;
+    return ((queryScore * (queryLength / stringLength)) + queryScore) / 2;
+  };
+
+  queryIsLastPathSegment = function(string, query) {
+    if (string[string.length - query.length - 1] === PathSeparator) {
+      return string.lastIndexOf(query) === string.length - query.length;
+    }
+  };
+
+  exports.match = function(string, query, stringOffset) {
+    var character, indexInQuery, indexInString, lowerCaseIndex, matches, minIndex, queryLength, stringLength, upperCaseIndex, _i, _ref, _results;
+    if (stringOffset == null) {
+      stringOffset = 0;
+    }
+    if (string === query) {
+      return (function() {
+        _results = [];
+        for (var _i = stringOffset, _ref = stringOffset + string.length; stringOffset <= _ref ? _i < _ref : _i > _ref; stringOffset <= _ref ? _i++ : _i--){ _results.push(_i); }
+        return _results;
+      }).apply(this);
+    }
+    queryLength = query.length;
+    stringLength = string.length;
+    indexInQuery = 0;
+    indexInString = 0;
+    matches = [];
+    while (indexInQuery < queryLength) {
+      character = query[indexInQuery++];
+      lowerCaseIndex = string.indexOf(character.toLowerCase());
+      upperCaseIndex = string.indexOf(character.toUpperCase());
+      minIndex = Math.min(lowerCaseIndex, upperCaseIndex);
+      if (minIndex === -1) {
+        minIndex = Math.max(lowerCaseIndex, upperCaseIndex);
+      }
+      indexInString = minIndex;
+      if (indexInString === -1) {
+        return [];
+      }
+      matches.push(stringOffset + indexInString);
+      stringOffset += indexInString + 1;
+      string = string.substring(indexInString + 1, stringLength);
+    }
+    return matches;
+  };
+
+}).call(this);
+
+},{"path":7}],5:[function(require,module,exports){
+(function() {
+  var PathSeparator, scorer;
+
+  PathSeparator = require('path').sep;
+
+  scorer = require('./scorer');
+
+  exports.basenameMatch = function(subject, subject_lw, prepQuery) {
+    var basePos, depth, end;
+    end = subject.length - 1;
+    while (subject[end] === PathSeparator) {
+      end--;
+    }
+    basePos = subject.lastIndexOf(PathSeparator, end);
+    if (basePos === -1) {
+      return [];
+    }
+    depth = prepQuery.depth;
+    while (depth-- > 0) {
+      basePos = subject.lastIndexOf(PathSeparator, basePos - 1);
+      if (basePos === -1) {
+        return [];
+      }
+    }
+    basePos++;
+    end++;
+    return exports.match(subject.slice(basePos, end), subject_lw.slice(basePos, end), prepQuery, basePos);
+  };
+
+  exports.mergeMatches = function(a, b) {
+    var ai, bj, i, j, m, n, out;
+    m = a.length;
+    n = b.length;
+    if (n === 0) {
+      return a.slice();
+    }
+    if (m === 0) {
+      return b.slice();
+    }
+    i = -1;
+    j = 0;
+    bj = b[j];
+    out = [];
+    while (++i < m) {
+      ai = a[i];
+      while (bj <= ai && ++j < n) {
+        if (bj < ai) {
+          out.push(bj);
+        }
+        bj = b[j];
+      }
+      out.push(ai);
+    }
+    while (j < n) {
+      out.push(b[j++]);
+    }
+    return out;
+  };
+
+  exports.match = function(subject, subject_lw, prepQuery, offset) {
+    var DIAGONAL, LEFT, STOP, UP, acro_score, align, backtrack, csc_diag, csc_row, csc_score, i, j, m, matches, move, n, pos, query, query_lw, score, score_diag, score_row, score_up, si_lw, start, trace;
+    if (offset == null) {
+      offset = 0;
+    }
+    query = prepQuery.query;
+    query_lw = prepQuery.query_lw;
+    m = subject.length;
+    n = query.length;
+    acro_score = scorer.scoreAcronyms(subject, subject_lw, query, query_lw).score;
+    score_row = new Array(n);
+    csc_row = new Array(n);
+    STOP = 0;
+    UP = 1;
+    LEFT = 2;
+    DIAGONAL = 3;
+    trace = new Array(m * n);
+    pos = -1;
+    j = -1;
+    while (++j < n) {
+      score_row[j] = 0;
+      csc_row[j] = 0;
+    }
+    i = -1;
+    while (++i < m) {
+      score = 0;
+      score_up = 0;
+      csc_diag = 0;
+      si_lw = subject_lw[i];
+      j = -1;
+      while (++j < n) {
+        csc_score = 0;
+        align = 0;
+        score_diag = score_up;
+        if (query_lw[j] === si_lw) {
+          start = scorer.isWordStart(i, subject, subject_lw);
+          csc_score = csc_diag > 0 ? csc_diag : scorer.scoreConsecutives(subject, subject_lw, query, query_lw, i, j, start);
+          align = score_diag + scorer.scoreCharacter(i, j, start, acro_score, csc_score);
+        }
+        score_up = score_row[j];
+        csc_diag = csc_row[j];
+        if (score > score_up) {
+          move = LEFT;
+        } else {
+          score = score_up;
+          move = UP;
+        }
+        if (align > score) {
+          score = align;
+          move = DIAGONAL;
+        } else {
+          csc_score = 0;
+        }
+        score_row[j] = score;
+        csc_row[j] = csc_score;
+        trace[++pos] = score > 0 ? move : STOP;
+      }
+    }
+    i = m - 1;
+    j = n - 1;
+    pos = i * n + j;
+    backtrack = true;
+    matches = [];
+    while (backtrack && i >= 0 && j >= 0) {
+      switch (trace[pos]) {
+        case UP:
+          i--;
+          pos -= n;
+          break;
+        case LEFT:
+          j--;
+          pos--;
+          break;
+        case DIAGONAL:
+          matches.push(i + offset);
+          j--;
+          i--;
+          pos -= n + 1;
+          break;
+        default:
+          backtrack = false;
+      }
+    }
+    matches.reverse();
+    return matches;
+  };
+
+}).call(this);
+
+},{"./scorer":6,"path":7}],6:[function(require,module,exports){
+(function() {
+  var AcronymResult, PathSeparator, Query, basenameScore, coreChars, countDir, doScore, emptyAcronymResult, file_coeff, isMatch, isSeparator, isWordEnd, isWordStart, miss_coeff, opt_char_re, pos_bonus, scoreAcronyms, scoreCharacter, scoreConsecutives, scoreExact, scoreExactMatch, scorePattern, scorePosition, scoreSize, tau_depth, tau_size, truncatedUpperCase, wm;
+
+  PathSeparator = require('path').sep;
+
+  wm = 150;
+
+  pos_bonus = 20;
+
+  tau_depth = 13;
+
+  tau_size = 85;
+
+  file_coeff = 1.2;
+
+  miss_coeff = 0.75;
+
+  opt_char_re = /[ _\-:\/\\]/g;
+
+  exports.coreChars = coreChars = function(query) {
+    return query.replace(opt_char_re, '');
+  };
+
+  exports.score = function(string, query, prepQuery, allowErrors) {
+    var score, string_lw;
+    if (prepQuery == null) {
+      prepQuery = new Query(query);
+    }
+    if (allowErrors == null) {
+      allowErrors = false;
+    }
+    if (!(allowErrors || isMatch(string, prepQuery.core_lw, prepQuery.core_up))) {
+      return 0;
+    }
+    string_lw = string.toLowerCase();
+    score = doScore(string, string_lw, prepQuery);
+    return Math.ceil(basenameScore(string, string_lw, prepQuery, score));
+  };
+
+  Query = (function() {
+    function Query(query) {
+      if (!(query != null ? query.length : void 0)) {
+        return null;
+      }
+      this.query = query;
+      this.query_lw = query.toLowerCase();
+      this.core = coreChars(query);
+      this.core_lw = this.core.toLowerCase();
+      this.core_up = truncatedUpperCase(this.core);
+      this.depth = countDir(query, query.length);
+    }
+
+    return Query;
+
+  })();
+
+  exports.prepQuery = function(query) {
+    return new Query(query);
+  };
+
+  exports.isMatch = isMatch = function(subject, query_lw, query_up) {
+    var i, j, m, n, qj_lw, qj_up, si;
+    m = subject.length;
+    n = query_lw.length;
+    if (!m || n > m) {
+      return false;
+    }
+    i = -1;
+    j = -1;
+    while (++j < n) {
+      qj_lw = query_lw[j];
+      qj_up = query_up[j];
+      while (++i < m) {
+        si = subject[i];
+        if (si === qj_lw || si === qj_up) {
+          break;
+        }
+      }
+      if (i === m) {
+        return false;
+      }
+    }
+    return true;
+  };
+
+  doScore = function(subject, subject_lw, prepQuery) {
+    var acro, acro_score, align, csc_diag, csc_row, csc_score, i, j, m, miss_budget, miss_left, mm, n, pos, query, query_lw, record_miss, score, score_diag, score_row, score_up, si_lw, start, sz;
+    query = prepQuery.query;
+    query_lw = prepQuery.query_lw;
+    m = subject.length;
+    n = query.length;
+    acro = scoreAcronyms(subject, subject_lw, query, query_lw);
+    acro_score = acro.score;
+    if (acro.count === n) {
+      return scoreExact(n, m, acro_score, acro.pos);
+    }
+    pos = subject_lw.indexOf(query_lw);
+    if (pos > -1) {
+      return scoreExactMatch(subject, subject_lw, query, query_lw, pos, n, m);
+    }
+    score_row = new Array(n);
+    csc_row = new Array(n);
+    sz = scoreSize(n, m);
+    miss_budget = Math.ceil(miss_coeff * n) + 5;
+    miss_left = miss_budget;
+    j = -1;
+    while (++j < n) {
+      score_row[j] = 0;
+      csc_row[j] = 0;
+    }
+    i = subject_lw.indexOf(query_lw[0]);
+    if (i > -1) {
+      i--;
+    }
+    mm = subject_lw.lastIndexOf(query_lw[n - 1], m);
+    if (mm > i) {
+      m = mm + 1;
+    }
+    while (++i < m) {
+      score = 0;
+      score_diag = 0;
+      csc_diag = 0;
+      si_lw = subject_lw[i];
+      record_miss = true;
+      j = -1;
+      while (++j < n) {
+        score_up = score_row[j];
+        if (score_up > score) {
+          score = score_up;
+        }
+        csc_score = 0;
+        if (query_lw[j] === si_lw) {
+          start = isWordStart(i, subject, subject_lw);
+          csc_score = csc_diag > 0 ? csc_diag : scoreConsecutives(subject, subject_lw, query, query_lw, i, j, start);
+          align = score_diag + scoreCharacter(i, j, start, acro_score, csc_score);
+          if (align > score) {
+            score = align;
+            miss_left = miss_budget;
+          } else {
+            if (record_miss && --miss_left <= 0) {
+              return score_row[n - 1] * sz;
+            }
+            record_miss = false;
+          }
+        }
+        score_diag = score_up;
+        csc_diag = csc_row[j];
+        csc_row[j] = csc_score;
+        score_row[j] = score;
+      }
+    }
+    return score * sz;
+  };
+
+  exports.isWordStart = isWordStart = function(pos, subject, subject_lw) {
+    var curr_s, prev_s;
+    if (pos === 0) {
+      return true;
+    }
+    curr_s = subject[pos];
+    prev_s = subject[pos - 1];
+    return isSeparator(curr_s) || isSeparator(prev_s) || (curr_s !== subject_lw[pos] && prev_s === subject_lw[pos - 1]);
+  };
+
+  exports.isWordEnd = isWordEnd = function(pos, subject, subject_lw, len) {
+    var curr_s, next_s;
+    if (pos === len - 1) {
+      return true;
+    }
+    curr_s = subject[pos];
+    next_s = subject[pos + 1];
+    return isSeparator(curr_s) || isSeparator(next_s) || (curr_s === subject_lw[pos] && next_s !== subject_lw[pos + 1]);
+  };
+
+  isSeparator = function(c) {
+    return c === ' ' || c === '.' || c === '-' || c === '_' || c === '/' || c === '\\';
+  };
+
+  scorePosition = function(pos) {
+    var sc;
+    if (pos < pos_bonus) {
+      sc = pos_bonus - pos;
+      return 100 + sc * sc;
+    } else {
+      return Math.max(100 + pos_bonus - pos, 0);
+    }
+  };
+
+  scoreSize = function(n, m) {
+    return tau_size / (tau_size + Math.abs(m - n));
+  };
+
+  scoreExact = function(n, m, quality, pos) {
+    return 2 * n * (wm * quality + scorePosition(pos)) * scoreSize(n, m);
+  };
+
+  exports.scorePattern = scorePattern = function(count, len, sameCase, start, end) {
+    var bonus, sz;
+    sz = count;
+    bonus = 6;
+    if (sameCase === count) {
+      bonus += 2;
+    }
+    if (start) {
+      bonus += 3;
+    }
+    if (end) {
+      bonus += 1;
+    }
+    if (count === len) {
+      if (start) {
+        if (sameCase === len) {
+          sz += 2;
+        } else {
+          sz += 1;
+        }
+      }
+      if (end) {
+        bonus += 1;
+      }
+    }
+    return sameCase + sz * (sz + bonus);
+  };
+
+  exports.scoreCharacter = scoreCharacter = function(i, j, start, acro_score, csc_score) {
+    var posBonus;
+    posBonus = scorePosition(i);
+    if (start) {
+      return posBonus + wm * ((acro_score > csc_score ? acro_score : csc_score) + 10);
+    }
+    return posBonus + wm * csc_score;
+  };
+
+  exports.scoreConsecutives = scoreConsecutives = function(subject, subject_lw, query, query_lw, i, j, start) {
+    var k, m, mi, n, nj, sameCase, startPos, sz;
+    m = subject.length;
+    n = query.length;
+    mi = m - i;
+    nj = n - j;
+    k = mi < nj ? mi : nj;
+    startPos = i;
+    sameCase = 0;
+    sz = 0;
+    if (query[j] === subject[i]) {
+      sameCase++;
+    }
+    while (++sz < k && query_lw[++j] === subject_lw[++i]) {
+      if (query[j] === subject[i]) {
+        sameCase++;
+      }
+    }
+    if (sz === 1) {
+      return 1 + 2 * sameCase;
+    }
+    return scorePattern(sz, n, sameCase, start, isWordEnd(i, subject, subject_lw, m));
+  };
+
+  exports.scoreExactMatch = scoreExactMatch = function(subject, subject_lw, query, query_lw, pos, n, m) {
+    var end, i, pos2, sameCase, start;
+    start = isWordStart(pos, subject, subject_lw);
+    if (!start) {
+      pos2 = subject_lw.indexOf(query_lw, pos + 1);
+      if (pos2 > -1) {
+        start = isWordStart(pos2, subject, subject_lw);
+        if (start) {
+          pos = pos2;
+        }
+      }
+    }
+    i = -1;
+    sameCase = 0;
+    while (++i < n) {
+      if (query[pos + i] === subject[i]) {
+        sameCase++;
+      }
+    }
+    end = isWordEnd(pos + n - 1, subject, subject_lw, m);
+    return scoreExact(n, m, scorePattern(n, n, sameCase, start, end), pos);
+  };
+
+  AcronymResult = (function() {
+    function AcronymResult(score, pos, count) {
+      this.score = score;
+      this.pos = pos;
+      this.count = count;
+    }
+
+    return AcronymResult;
+
+  })();
+
+  emptyAcronymResult = new AcronymResult(0, 0.1, 0);
+
+  exports.scoreAcronyms = scoreAcronyms = function(subject, subject_lw, query, query_lw) {
+    var count, i, j, m, n, pos, qj_lw, sameCase, score;
+    m = subject.length;
+    n = query.length;
+    if (!(m > 1 && n > 1)) {
+      return emptyAcronymResult;
+    }
+    count = 0;
+    pos = 0;
+    sameCase = 0;
+    i = -1;
+    j = -1;
+    while (++j < n) {
+      qj_lw = query_lw[j];
+      while (++i < m) {
+        if (qj_lw === subject_lw[i] && isWordStart(i, subject, subject_lw)) {
+          if (query[j] === subject[i]) {
+            sameCase++;
+          }
+          pos += i;
+          count++;
+          break;
+        }
+      }
+      if (i === m) {
+        break;
+      }
+    }
+    if (count < 2) {
+      return emptyAcronymResult;
+    }
+    score = scorePattern(count, n, sameCase, true, false);
+    return new AcronymResult(score, pos / count, count);
+  };
+
+  basenameScore = function(subject, subject_lw, prepQuery, fullPathScore) {
+    var alpha, basePathScore, basePos, depth, end;
+    if (fullPathScore === 0) {
+      return 0;
+    }
+    end = subject.length - 1;
+    while (subject[end] === PathSeparator) {
+      end--;
+    }
+    basePos = subject.lastIndexOf(PathSeparator, end);
+    if (basePos === -1) {
+      return fullPathScore;
+    }
+    depth = prepQuery.depth;
+    while (depth-- > 0) {
+      basePos = subject.lastIndexOf(PathSeparator, basePos - 1);
+      if (basePos === -1) {
+        return fullPathScore;
+      }
+    }
+    basePos++;
+    end++;
+    basePathScore = doScore(subject.slice(basePos, end), subject_lw.slice(basePos, end), prepQuery);
+    alpha = 0.5 * tau_depth / (tau_depth + countDir(subject, end + 1));
+    return alpha * basePathScore + (1 - alpha) * fullPathScore * scoreSize(0, file_coeff * (end - basePos));
+  };
+
+  exports.countDir = countDir = function(path, end) {
+    var count, i;
+    if (end < 1) {
+      return 0;
+    }
+    count = 0;
+    i = -1;
+    while (++i < end && path[i] === PathSeparator) {
+      continue;
+    }
+    while (++i < end) {
+      if (path[i] === PathSeparator) {
+        count++;
+        while (++i < end && path[i] === PathSeparator) {
+          continue;
+        }
+      }
+    }
+    return count;
+  };
+
+  truncatedUpperCase = function(str) {
+    var char, upper, _i, _len;
+    upper = "";
+    for (_i = 0, _len = str.length; _i < _len; _i++) {
+      char = str[_i];
+      upper += char.toUpperCase()[0];
+    }
+    return upper;
+  };
+
+}).call(this);
+
+},{"path":7}],7:[function(require,module,exports){
+(function (process){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// resolves . and .. elements in a path array with directory names there
+// must be no slashes, empty elements, or device names (c:\) in the array
+// (so also no leading and trailing slashes - it does not distinguish
+// relative and absolute paths)
+function normalizeArray(parts, allowAboveRoot) {
+  // if the path tries to go above the root, `up` ends up > 0
+  var up = 0;
+  for (var i = parts.length - 1; i >= 0; i--) {
+    var last = parts[i];
+    if (last === '.') {
+      parts.splice(i, 1);
+    } else if (last === '..') {
+      parts.splice(i, 1);
+      up++;
+    } else if (up) {
+      parts.splice(i, 1);
+      up--;
+    }
+  }
+
+  // if the path is allowed to go above the root, restore leading ..s
+  if (allowAboveRoot) {
+    for (; up--; up) {
+      parts.unshift('..');
+    }
+  }
+
+  return parts;
+}
+
+// Split a filename into [root, dir, basename, ext], unix version
+// 'root' is just a slash, or nothing.
+var splitPathRe =
+    /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
+var splitPath = function(filename) {
+  return splitPathRe.exec(filename).slice(1);
+};
+
+// path.resolve([from ...], to)
+// posix version
+exports.resolve = function() {
+  var resolvedPath = '',
+      resolvedAbsolute = false;
+
+  for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
+    var path = (i >= 0) ? arguments[i] : process.cwd();
+
+    // Skip empty and invalid entries
+    if (typeof path !== 'string') {
+      throw new TypeError('Arguments to path.resolve must be strings');
+    } else if (!path) {
+      continue;
+    }
+
+    resolvedPath = path + '/' + resolvedPath;
+    resolvedAbsolute = path.charAt(0) === '/';
+  }
+
+  // At this point the path should be resolved to a full absolute path, but
+  // handle relative paths to be safe (might happen when process.cwd() fails)
+
+  // Normalize the path
+  resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
+    return !!p;
+  }), !resolvedAbsolute).join('/');
+
+  return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+};
+
+// path.normalize(path)
+// posix version
+exports.normalize = function(path) {
+  var isAbsolute = exports.isAbsolute(path),
+      trailingSlash = substr(path, -1) === '/';
+
+  // Normalize the path
+  path = normalizeArray(filter(path.split('/'), function(p) {
+    return !!p;
+  }), !isAbsolute).join('/');
+
+  if (!path && !isAbsolute) {
+    path = '.';
+  }
+  if (path && trailingSlash) {
+    path += '/';
+  }
+
+  return (isAbsolute ? '/' : '') + path;
+};
+
+// posix version
+exports.isAbsolute = function(path) {
+  return path.charAt(0) === '/';
+};
+
+// posix version
+exports.join = function() {
+  var paths = Array.prototype.slice.call(arguments, 0);
+  return exports.normalize(filter(paths, function(p, index) {
+    if (typeof p !== 'string') {
+      throw new TypeError('Arguments to path.join must be strings');
+    }
+    return p;
+  }).join('/'));
+};
+
+
+// path.relative(from, to)
+// posix version
+exports.relative = function(from, to) {
+  from = exports.resolve(from).substr(1);
+  to = exports.resolve(to).substr(1);
+
+  function trim(arr) {
+    var start = 0;
+    for (; start < arr.length; start++) {
+      if (arr[start] !== '') break;
+    }
+
+    var end = arr.length - 1;
+    for (; end >= 0; end--) {
+      if (arr[end] !== '') break;
+    }
+
+    if (start > end) return [];
+    return arr.slice(start, end - start + 1);
+  }
+
+  var fromParts = trim(from.split('/'));
+  var toParts = trim(to.split('/'));
+
+  var length = Math.min(fromParts.length, toParts.length);
+  var samePartsLength = length;
+  for (var i = 0; i < length; i++) {
+    if (fromParts[i] !== toParts[i]) {
+      samePartsLength = i;
+      break;
+    }
+  }
+
+  var outputParts = [];
+  for (var i = samePartsLength; i < fromParts.length; i++) {
+    outputParts.push('..');
+  }
+
+  outputParts = outputParts.concat(toParts.slice(samePartsLength));
+
+  return outputParts.join('/');
+};
+
+exports.sep = '/';
+exports.delimiter = ':';
+
+exports.dirname = function(path) {
+  var result = splitPath(path),
+      root = result[0],
+      dir = result[1];
+
+  if (!root && !dir) {
+    // No dirname whatsoever
+    return '.';
+  }
+
+  if (dir) {
+    // It has a dirname, strip trailing slash
+    dir = dir.substr(0, dir.length - 1);
+  }
+
+  return root + dir;
+};
+
+
+exports.basename = function(path, ext) {
+  var f = splitPath(path)[2];
+  // TODO: make this comparison case-insensitive on windows?
+  if (ext && f.substr(-1 * ext.length) === ext) {
+    f = f.substr(0, f.length - ext.length);
+  }
+  return f;
+};
+
+
+exports.extname = function(path) {
+  return splitPath(path)[3];
+};
+
+function filter (xs, f) {
+    if (xs.filter) return xs.filter(f);
+    var res = [];
+    for (var i = 0; i < xs.length; i++) {
+        if (f(xs[i], i, xs)) res.push(xs[i]);
+    }
+    return res;
+}
+
+// String.prototype.substr - negative index don't work in IE8
+var substr = 'ab'.substr(-1) === 'b'
+    ? function (str, start, len) { return str.substr(start, len) }
+    : function (str, start, len) {
+        if (start < 0) start = str.length + start;
+        return str.substr(start, len);
+    }
+;
+
+}).call(this,require('_process'))
+},{"_process":8}],8:[function(require,module,exports){
+// shim for using process in browser
+
+var process = module.exports = {};
+var queue = [];
+var draining = false;
+var currentQueue;
+var queueIndex = -1;
+
+function cleanUpNextTick() {
+    draining = false;
+    if (currentQueue.length) {
+        queue = currentQueue.concat(queue);
+    } else {
+        queueIndex = -1;
+    }
+    if (queue.length) {
+        drainQueue();
+    }
+}
+
+function drainQueue() {
+    if (draining) {
+        return;
+    }
+    var timeout = setTimeout(cleanUpNextTick);
+    draining = true;
+
+    var len = queue.length;
+    while(len) {
+        currentQueue = queue;
+        queue = [];
+        while (++queueIndex < len) {
+            if (currentQueue) {
+                currentQueue[queueIndex].run();
+            }
+        }
+        queueIndex = -1;
+        len = queue.length;
+    }
+    currentQueue = null;
+    draining = false;
+    clearTimeout(timeout);
+}
+
+process.nextTick = function (fun) {
+    var args = new Array(arguments.length - 1);
+    if (arguments.length > 1) {
+        for (var i = 1; i < arguments.length; i++) {
+            args[i - 1] = arguments[i];
+        }
+    }
+    queue.push(new Item(fun, args));
+    if (queue.length === 1 && !draining) {
+        setTimeout(drainQueue, 0);
+    }
+};
+
+// v8 likes predictible objects
+function Item(fun, array) {
+    this.fun = fun;
+    this.array = array;
+}
+Item.prototype.run = function () {
+    this.fun.apply(null, this.array);
+};
+process.title = 'browser';
+process.browser = true;
+process.env = {};
+process.argv = [];
+process.version = ''; // empty string to avoid regexp issues
+process.versions = {};
+
+function noop() {}
+
+process.on = noop;
+process.addListener = noop;
+process.once = noop;
+process.off = noop;
+process.removeListener = noop;
+process.removeAllListeners = noop;
+process.emit = noop;
+
+process.binding = function (name) {
+    throw new Error('process.binding is not supported');
+};
+
+process.cwd = function () { return '/' };
+process.chdir = function (dir) {
+    throw new Error('process.chdir is not supported');
+};
+process.umask = function() { return 0; };
+
+},{}]},{},[1]);
diff --git a/vendor/assets/javascripts/fuzzaldrin-plus.min.js b/vendor/assets/javascripts/fuzzaldrin-plus.min.js
deleted file mode 100644
index 3f25c2d8373cfd9f2696a4993d97cf7d140d3cb9..0000000000000000000000000000000000000000
--- a/vendor/assets/javascripts/fuzzaldrin-plus.min.js
+++ /dev/null
@@ -1 +0,0 @@
-(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){(function(){var PathSeparator,legacy_scorer,pluckCandidates,scorer,sortCandidates;scorer=require('./scorer');legacy_scorer=require('./legacy');pluckCandidates=function(a){return a.candidate};sortCandidates=function(a,b){return b.score-a.score};PathSeparator=require('path').sep;module.exports=function(candidates,query,arg){var allowErrors,bAllowErrors,bKey,candidate,coreQuery,i,j,key,legacy,len,len1,maxInners,maxResults,prepQuery,queryHasSlashes,ref,score,scoredCandidates,spotLeft,string;ref=arg!=null?arg:{},key=ref.key,maxResults=ref.maxResults,maxInners=ref.maxInners,allowErrors=ref.allowErrors,legacy=ref.legacy;scoredCandidates=[];spotLeft=(maxInners!=null)&&maxInners>0?maxInners:candidates.length;bAllowErrors=!!allowErrors;bKey=key!=null;prepQuery=scorer.prepQuery(query);if(!legacy){for(i=0,len=candidates.length;i<len;i++){candidate=candidates[i];string=bKey?candidate[key]:candidate;if(!string){continue}score=scorer.score(string,query,prepQuery,bAllowErrors);if(score>0){scoredCandidates.push({candidate:candidate,score:score});if(!--spotLeft){break}}}}else{queryHasSlashes=prepQuery.depth>0;coreQuery=prepQuery.core;for(j=0,len1=candidates.length;j<len1;j++){candidate=candidates[j];string=key!=null?candidate[key]:candidate;if(!string){continue}score=legacy_scorer.score(string,coreQuery,queryHasSlashes);if(!queryHasSlashes){score=legacy_scorer.basenameScore(string,coreQuery,score)}if(score>0){scoredCandidates.push({candidate:candidate,score:score})}}}scoredCandidates.sort(sortCandidates);candidates=scoredCandidates.map(pluckCandidates);if(maxResults!=null){candidates=candidates.slice(0,maxResults)}return candidates}}).call(this)},{"./legacy":4,"./scorer":6,"path":7}],2:[function(require,module,exports){(function(){var PathSeparator,filter,legacy_scorer,matcher,prepQueryCache,scorer;scorer=require('./scorer');legacy_scorer=require('./legacy');filter=require('./filter');matcher=require('./matcher');PathSeparator=require('path').sep;prepQueryCache=null;module.exports={filter:function(candidates,query,options){if(!((query!=null?query.length:void 0)&&(candidates!=null?candidates.length:void 0))){return[]}return filter(candidates,query,options)},prepQuery:function(query){return scorer.prepQuery(query)},score:function(string,query,prepQuery,arg){var allowErrors,coreQuery,legacy,queryHasSlashes,ref,score;ref=arg!=null?arg:{},allowErrors=ref.allowErrors,legacy=ref.legacy;if(!((string!=null?string.length:void 0)&&(query!=null?query.length:void 0))){return 0}if(prepQuery==null){prepQuery=prepQueryCache&&prepQueryCache.query===query?prepQueryCache:(prepQueryCache=scorer.prepQuery(query))}if(!legacy){score=scorer.score(string,query,prepQuery,!!allowErrors)}else{queryHasSlashes=prepQuery.depth>0;coreQuery=prepQuery.core;score=legacy_scorer.score(string,coreQuery,queryHasSlashes);if(!queryHasSlashes){score=legacy_scorer.basenameScore(string,coreQuery,score)}}return score},match:function(string,query,prepQuery,arg){var allowErrors,baseMatches,i,matches,query_lw,ref,results,string_lw;allowErrors=(arg!=null?arg:{}).allowErrors;if(!string){return[]}if(!query){return[]}if(string===query){return(function(){results=[];for(var i=0,ref=string.length;0<=ref?i<ref:i>ref;0<=ref?i++:i--){results.push(i)}return results}).apply(this)}if(prepQuery==null){prepQuery=prepQueryCache&&prepQueryCache.query===query?prepQueryCache:(prepQueryCache=scorer.prepQuery(query))}if(!(allowErrors||scorer.isMatch(string,prepQuery.core_lw,prepQuery.core_up))){return[]}string_lw=string.toLowerCase();query_lw=prepQuery.query_lw;matches=matcher.match(string,string_lw,prepQuery);if(matches.length===0){return matches}if(string.indexOf(PathSeparator)>-1){baseMatches=matcher.basenameMatch(string,string_lw,prepQuery);matches=matcher.mergeMatches(matches,baseMatches)}return matches}}}).call(this)},{"./filter":1,"./legacy":4,"./matcher":5,"./scorer":6,"path":7}],3:[function(require,module,exports){fuzzaldrinPlus=require('./fuzzaldrin')},{"./fuzzaldrin":2}],4:[function(require,module,exports){(function(){var PathSeparator,queryIsLastPathSegment;PathSeparator=require('path').sep;exports.basenameScore=function(string,query,score){var base,depth,index,lastCharacter,segmentCount,slashCount;index=string.length-1;while(string[index]===PathSeparator){index--}slashCount=0;lastCharacter=index;base=null;while(index>=0){if(string[index]===PathSeparator){slashCount++;if(base==null){base=string.substring(index+1,lastCharacter+1)}}else if(index===0){if(lastCharacter<string.length-1){if(base==null){base=string.substring(0,lastCharacter+1)}}else{if(base==null){base=string}}}index--}if(base===string){score*=2}else if(base){score+=exports.score(base,query)}segmentCount=slashCount+1;depth=Math.max(1,10-segmentCount);score*=depth*0.01;return score};exports.score=function(string,query){var character,characterScore,indexInQuery,indexInString,lowerCaseIndex,minIndex,queryLength,queryScore,ref,stringLength,totalCharacterScore,upperCaseIndex;if(string===query){return 1}if(queryIsLastPathSegment(string,query)){return 1}totalCharacterScore=0;queryLength=query.length;stringLength=string.length;indexInQuery=0;indexInString=0;while(indexInQuery<queryLength){character=query[indexInQuery++];lowerCaseIndex=string.indexOf(character.toLowerCase());upperCaseIndex=string.indexOf(character.toUpperCase());minIndex=Math.min(lowerCaseIndex,upperCaseIndex);if(minIndex===-1){minIndex=Math.max(lowerCaseIndex,upperCaseIndex)}indexInString=minIndex;if(indexInString===-1){return 0}characterScore=0.1;if(string[indexInString]===character){characterScore+=0.1}if(indexInString===0||string[indexInString-1]===PathSeparator){characterScore+=0.8}else if((ref=string[indexInString-1])==='-'||ref==='_'||ref===' '){characterScore+=0.7}string=string.substring(indexInString+1,stringLength);totalCharacterScore+=characterScore}queryScore=totalCharacterScore/queryLength;return((queryScore*(queryLength/stringLength))+queryScore)/2};queryIsLastPathSegment=function(string,query){if(string[string.length-query.length-1]===PathSeparator){return string.lastIndexOf(query)===string.length-query.length}};exports.match=function(string,query,stringOffset){var character,i,indexInQuery,indexInString,lowerCaseIndex,matches,minIndex,queryLength,ref,results,stringLength,upperCaseIndex;if(stringOffset==null){stringOffset=0}if(string===query){return(function(){results=[];for(var i=stringOffset,ref=stringOffset+string.length;stringOffset<=ref?i<ref:i>ref;stringOffset<=ref?i++:i--){results.push(i)}return results}).apply(this)}queryLength=query.length;stringLength=string.length;indexInQuery=0;indexInString=0;matches=[];while(indexInQuery<queryLength){character=query[indexInQuery++];lowerCaseIndex=string.indexOf(character.toLowerCase());upperCaseIndex=string.indexOf(character.toUpperCase());minIndex=Math.min(lowerCaseIndex,upperCaseIndex);if(minIndex===-1){minIndex=Math.max(lowerCaseIndex,upperCaseIndex)}indexInString=minIndex;if(indexInString===-1){return[]}matches.push(stringOffset+indexInString);stringOffset+=indexInString+1;string=string.substring(indexInString+1,stringLength)}return matches}}).call(this)},{"path":7}],5:[function(require,module,exports){(function(){var PathSeparator,scorer;PathSeparator=require('path').sep;scorer=require('./scorer');exports.basenameMatch=function(subject,subject_lw,prepQuery){var basePos,depth,end;end=subject.length-1;while(subject[end]===PathSeparator){end--}basePos=subject.lastIndexOf(PathSeparator,end);if(basePos===-1){return[]}depth=prepQuery.depth;while(depth-->0){basePos=subject.lastIndexOf(PathSeparator,basePos-1);if(basePos===-1){return[]}}basePos++;end++;return exports.match(subject.slice(basePos,end),subject_lw.slice(basePos,end),prepQuery,basePos)};exports.mergeMatches=function(a,b){var ai,bj,i,j,m,n,out;m=a.length;n=b.length;if(n===0){return a.slice()}if(m===0){return b.slice()}i=-1;j=0;bj=b[j];out=[];while(++i<m){ai=a[i];while(bj<=ai&&++j<n){if(bj<ai){out.push(bj)}bj=b[j]}out.push(ai)}while(j<n){out.push(b[j++])}return out};exports.match=function(subject,subject_lw,prepQuery,offset){var DIAGONAL,LEFT,STOP,UP,acro_score,align,backtrack,csc_diag,csc_row,csc_score,i,j,m,matches,move,n,pos,query,query_lw,score,score_diag,score_row,score_up,si_lw,start,trace;if(offset==null){offset=0}query=prepQuery.query;query_lw=prepQuery.query_lw;m=subject.length;n=query.length;acro_score=scorer.scoreAcronyms(subject,subject_lw,query,query_lw).score;score_row=new Array(n);csc_row=new Array(n);STOP=0;UP=1;LEFT=2;DIAGONAL=3;trace=new Array(m*n);pos=-1;j=-1;while(++j<n){score_row[j]=0;csc_row[j]=0}i=-1;while(++i<m){score=0;score_up=0;csc_diag=0;si_lw=subject_lw[i];j=-1;while(++j<n){csc_score=0;align=0;score_diag=score_up;if(query_lw[j]===si_lw){start=scorer.isWordStart(i,subject,subject_lw);csc_score=csc_diag>0?csc_diag:scorer.scoreConsecutives(subject,subject_lw,query,query_lw,i,j,start);align=score_diag+scorer.scoreCharacter(i,j,start,acro_score,csc_score)}score_up=score_row[j];csc_diag=csc_row[j];if(score>score_up){move=LEFT}else{score=score_up;move=UP}if(align>score){score=align;move=DIAGONAL}else{csc_score=0}score_row[j]=score;csc_row[j]=csc_score;trace[++pos]=score>0?move:STOP}}i=m-1;j=n-1;pos=i*n+j;backtrack=true;matches=[];while(backtrack&&i>=0&&j>=0){switch(trace[pos]){case UP:i--;pos-=n;break;case LEFT:j--;pos--;break;case DIAGONAL:matches.push(i+offset);j--;i--;pos-=n+1;break;default:backtrack=false}}matches.reverse();return matches}}).call(this)},{"./scorer":6,"path":7}],6:[function(require,module,exports){(function(){var AcronymResult,PathSeparator,Query,basenameScore,coreChars,countDir,doScore,emptyAcronymResult,file_coeff,isMatch,isSeparator,isWordEnd,isWordStart,miss_coeff,opt_char_re,pos_bonus,scoreAcronyms,scoreCharacter,scoreConsecutives,scoreExact,scoreExactMatch,scorePattern,scorePosition,scoreSize,tau_depth,tau_size,truncatedUpperCase,wm;PathSeparator=require('path').sep;wm=150;pos_bonus=20;tau_depth=13;tau_size=85;file_coeff=1.2;miss_coeff=0.75;opt_char_re=/[ _\-:\/\\]/g;exports.coreChars=coreChars=function(query){return query.replace(opt_char_re,'')};exports.score=function(string,query,prepQuery,allowErrors){var score,string_lw;if(prepQuery==null){prepQuery=new Query(query)}if(allowErrors==null){allowErrors=false}if(!(allowErrors||isMatch(string,prepQuery.core_lw,prepQuery.core_up))){return 0}string_lw=string.toLowerCase();score=doScore(string,string_lw,prepQuery);return Math.ceil(basenameScore(string,string_lw,prepQuery,score))};Query=(function(){function Query(query){if(!(query!=null?query.length:void 0)){return null}this.query=query;this.query_lw=query.toLowerCase();this.core=coreChars(query);this.core_lw=this.core.toLowerCase();this.core_up=truncatedUpperCase(this.core);this.depth=countDir(query,query.length)}return Query})();exports.prepQuery=function(query){return new Query(query)};exports.isMatch=isMatch=function(subject,query_lw,query_up){var i,j,m,n,qj_lw,qj_up,si;m=subject.length;n=query_lw.length;if(!m||!n||n>m){return false}i=-1;j=-1;while(++j<n){qj_lw=query_lw[j];qj_up=query_up[j];while(++i<m){si=subject[i];if(si===qj_lw||si===qj_up){break}}if(i===m){return false}}return true};doScore=function(subject,subject_lw,prepQuery){var acro,acro_score,align,csc_diag,csc_row,csc_score,i,j,m,miss_budget,miss_left,mm,n,pos,query,query_lw,record_miss,score,score_diag,score_row,score_up,si_lw,start,sz;query=prepQuery.query;query_lw=prepQuery.query_lw;m=subject.length;n=query.length;acro=scoreAcronyms(subject,subject_lw,query,query_lw);acro_score=acro.score;if(acro.count===n){return scoreExact(n,m,acro_score,acro.pos)}pos=subject_lw.indexOf(query_lw);if(pos>-1){return scoreExactMatch(subject,subject_lw,query,query_lw,pos,n,m)}score_row=new Array(n);csc_row=new Array(n);sz=scoreSize(n,m);miss_budget=Math.ceil(miss_coeff*n)+5;miss_left=miss_budget;j=-1;while(++j<n){score_row[j]=0;csc_row[j]=0}i=subject_lw.indexOf(query_lw[0]);if(i>-1){i--}mm=subject_lw.lastIndexOf(query_lw[n-1],m);if(mm>i){m=mm+1}while(++i<m){score=0;score_diag=0;csc_diag=0;si_lw=subject_lw[i];record_miss=true;j=-1;while(++j<n){score_up=score_row[j];if(score_up>score){score=score_up}csc_score=0;if(query_lw[j]===si_lw){start=isWordStart(i,subject,subject_lw);csc_score=csc_diag>0?csc_diag:scoreConsecutives(subject,subject_lw,query,query_lw,i,j,start);align=score_diag+scoreCharacter(i,j,start,acro_score,csc_score);if(align>score){score=align;miss_left=miss_budget}else{if(record_miss&&--miss_left<=0){return score_row[n-1]*sz}record_miss=false}}score_diag=score_up;csc_diag=csc_row[j];csc_row[j]=csc_score;score_row[j]=score}}return score*sz};exports.isWordStart=isWordStart=function(pos,subject,subject_lw){var curr_s,prev_s;if(pos===0){return true}curr_s=subject[pos];prev_s=subject[pos-1];return isSeparator(curr_s)||isSeparator(prev_s)||(curr_s!==subject_lw[pos]&&prev_s===subject_lw[pos-1])};exports.isWordEnd=isWordEnd=function(pos,subject,subject_lw,len){var curr_s,next_s;if(pos===len-1){return true}curr_s=subject[pos];next_s=subject[pos+1];return isSeparator(curr_s)||isSeparator(next_s)||(curr_s===subject_lw[pos]&&next_s!==subject_lw[pos+1])};isSeparator=function(c){return c===' '||c==='.'||c==='-'||c==='_'||c==='/'||c==='\\'};scorePosition=function(pos){var sc;if(pos<pos_bonus){sc=pos_bonus-pos;return 100+sc*sc}else{return Math.max(100+pos_bonus-pos,0)}};scoreSize=function(n,m){return tau_size/(tau_size+Math.abs(m-n))};scoreExact=function(n,m,quality,pos){return 2*n*(wm*quality+scorePosition(pos))*scoreSize(n,m)};exports.scorePattern=scorePattern=function(count,len,sameCase,start,end){var bonus,sz;sz=count;bonus=6;if(sameCase===count){bonus+=2}if(start){bonus+=3}if(end){bonus+=1}if(count===len){if(start){if(sameCase===len){sz+=2}else{sz+=1}}if(end){bonus+=1}}return sameCase+sz*(sz+bonus)};exports.scoreCharacter=scoreCharacter=function(i,j,start,acro_score,csc_score){var posBonus;posBonus=scorePosition(i);if(start){return posBonus+wm*((acro_score>csc_score?acro_score:csc_score)+10)}return posBonus+wm*csc_score};exports.scoreConsecutives=scoreConsecutives=function(subject,subject_lw,query,query_lw,i,j,start){var k,m,mi,n,nj,sameCase,startPos,sz;m=subject.length;n=query.length;mi=m-i;nj=n-j;k=mi<nj?mi:nj;startPos=i;sameCase=0;sz=0;if(query[j]===subject[i]){sameCase++}while(++sz<k&&query_lw[++j]===subject_lw[++i]){if(query[j]===subject[i]){sameCase++}}if(sz===1){return 1+2*sameCase}return scorePattern(sz,n,sameCase,start,isWordEnd(i,subject,subject_lw,m))};exports.scoreExactMatch=scoreExactMatch=function(subject,subject_lw,query,query_lw,pos,n,m){var end,i,pos2,sameCase,start;start=isWordStart(pos,subject,subject_lw);if(!start){pos2=subject_lw.indexOf(query_lw,pos+1);if(pos2>-1){start=isWordStart(pos2,subject,subject_lw);if(start){pos=pos2}}}i=-1;sameCase=0;while(++i<n){if(query[pos+i]===subject[i]){sameCase++}}end=isWordEnd(pos+n-1,subject,subject_lw,m);return scoreExact(n,m,scorePattern(n,n,sameCase,start,end),pos)};AcronymResult=(function(){function AcronymResult(score1,pos1,count1){this.score=score1;this.pos=pos1;this.count=count1}return AcronymResult})();emptyAcronymResult=new AcronymResult(0,0.1,0);exports.scoreAcronyms=scoreAcronyms=function(subject,subject_lw,query,query_lw){var count,i,j,m,n,pos,qj_lw,sameCase,score;m=subject.length;n=query.length;if(!(m>1&&n>1)){return emptyAcronymResult}count=0;pos=0;sameCase=0;i=-1;j=-1;while(++j<n){qj_lw=query_lw[j];while(++i<m){if(qj_lw===subject_lw[i]&&isWordStart(i,subject,subject_lw)){if(query[j]===subject[i]){sameCase++}pos+=i;count++;break}}if(i===m){break}}if(count<2){return emptyAcronymResult}score=scorePattern(count,n,sameCase,true,false);return new AcronymResult(score,pos/count,count)};basenameScore=function(subject,subject_lw,prepQuery,fullPathScore){var alpha,basePathScore,basePos,depth,end;if(fullPathScore===0){return 0}end=subject.length-1;while(subject[end]===PathSeparator){end--}basePos=subject.lastIndexOf(PathSeparator,end);if(basePos===-1){return fullPathScore}depth=prepQuery.depth;while(depth-->0){basePos=subject.lastIndexOf(PathSeparator,basePos-1);if(basePos===-1){return fullPathScore}}basePos++;end++;basePathScore=doScore(subject.slice(basePos,end),subject_lw.slice(basePos,end),prepQuery);alpha=0.5*tau_depth/(tau_depth+countDir(subject,end+1));return alpha*basePathScore+(1-alpha)*fullPathScore*scoreSize(0,file_coeff*(end-basePos))};exports.countDir=countDir=function(path,end){var count,i;if(end<1){return 0}count=0;i=-1;while(++i<end&&path[i]===PathSeparator){continue}while(++i<end){if(path[i]===PathSeparator){count++;while(++i<end&&path[i]===PathSeparator){continue}}}return count};truncatedUpperCase=function(str){var char,l,len1,upper;upper="";for(l=0,len1=str.length;l<len1;l++){char=str[l];upper+=char.toUpperCase()[0]}return upper}}).call(this)},{"path":7}],7:[function(require,module,exports){(function(process){function normalizeArray(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==='.'){parts.splice(i,1)}else if(last==='..'){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up--;up){parts.unshift('..')}}return parts}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;var splitPath=function(filename){return splitPathRe.exec(filename).slice(1)};exports.resolve=function(){var resolvedPath='',resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=(i>=0)?arguments[i]:process.cwd();if(typeof path!=='string'){throw new TypeError('Arguments to path.resolve must be strings');}else if(!path){continue}resolvedPath=path+'/'+resolvedPath;resolvedAbsolute=path.charAt(0)==='/'}resolvedPath=normalizeArray(filter(resolvedPath.split('/'),function(p){return!!p}),!resolvedAbsolute).join('/');return((resolvedAbsolute?'/':'')+resolvedPath)||'.'};exports.normalize=function(path){var isAbsolute=exports.isAbsolute(path),trailingSlash=substr(path,-1)==='/';path=normalizeArray(filter(path.split('/'),function(p){return!!p}),!isAbsolute).join('/');if(!path&&!isAbsolute){path='.'}if(path&&trailingSlash){path+='/'}return(isAbsolute?'/':'')+path};exports.isAbsolute=function(path){return path.charAt(0)==='/'};exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){if(typeof p!=='string'){throw new TypeError('Arguments to path.join must be strings');}return p}).join('/'))};exports.relative=function(from,to){from=exports.resolve(from).substr(1);to=exports.resolve(to).substr(1);function trim(arr){var start=0;for(;start<arr.length;start++){if(arr[start]!=='')break}var end=arr.length-1;for(;end>=0;end--){if(arr[end]!=='')break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split('/'));var toParts=trim(to.split('/'));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i<length;i++){if(fromParts[i]!==toParts[i]){samePartsLength=i;break}}var outputParts=[];for(var i=samePartsLength;i<fromParts.length;i++){outputParts.push('..')}outputParts=outputParts.concat(toParts.slice(samePartsLength));return outputParts.join('/')};exports.sep='/';exports.delimiter=':';exports.dirname=function(path){var result=splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return'.'}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir};exports.basename=function(path,ext){var f=splitPath(path)[2];if(ext&&f.substr(-1*ext.length)===ext){f=f.substr(0,f.length-ext.length)}return f};exports.extname=function(path){return splitPath(path)[3]};function filter(xs,f){if(xs.filter)return xs.filter(f);var res=[];for(var i=0;i<xs.length;i++){if(f(xs[i],i,xs))res.push(xs[i])}return res}var substr='ab'.substr(-1)==='b'?function(str,start,len){return str.substr(start,len)}:function(str,start,len){if(start<0)start=str.length+start;return str.substr(start,len)}}).call(this,require('_process'))},{"_process":8}],8:[function(require,module,exports){var process=module.exports={};var queue=[];var draining=false;var currentQueue;var queueIndex=-1;function cleanUpNextTick(){draining=false;if(currentQueue.length){queue=currentQueue.concat(queue)}else{queueIndex=-1}if(queue.length){drainQueue()}}function drainQueue(){if(draining){return}var timeout=setTimeout(cleanUpNextTick);draining=true;var len=queue.length;while(len){currentQueue=queue;queue=[];while(++queueIndex<len){if(currentQueue){currentQueue[queueIndex].run()}}queueIndex=-1;len=queue.length}currentQueue=null;draining=false;clearTimeout(timeout)}process.nextTick=function(fun){var args=new Array(arguments.length-1);if(arguments.length>1){for(var i=1;i<arguments.length;i++){args[i-1]=arguments[i]}}queue.push(new Item(fun,args));if(queue.length===1&&!draining){setTimeout(drainQueue,0)}};function Item(fun,array){this.fun=fun;this.array=array}Item.prototype.run=function(){this.fun.apply(null,this.array)};process.title='browser';process.browser=true;process.env={};process.argv=[];process.version='';process.versions={};function noop(){}process.on=noop;process.addListener=noop;process.once=noop;process.off=noop;process.removeListener=noop;process.removeAllListeners=noop;process.emit=noop;process.binding=function(name){throw new Error('process.binding is not supported');};process.cwd=function(){return'/'};process.chdir=function(dir){throw new Error('process.chdir is not supported');};process.umask=function(){return 0}},{}]},{},[3]);
diff --git a/vendor/assets/javascripts/g.bar-min.js b/vendor/assets/javascripts/g.bar-min.js
deleted file mode 100644
index 7620dabda74366adba1c781d313a4bd26bdcc6c9..0000000000000000000000000000000000000000
--- a/vendor/assets/javascripts/g.bar-min.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/*!
- * g.Raphael 0.5 - Charting library, based on Raphaël
- *
- * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
- * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
- * From: https://github.com/jhurt/g.raphael/blob/master/g.bar.js
- */
-(function(){function c(c,d,e,f,g,h,i,j){var k,l={round:"round",sharp:"sharp",soft:"soft",square:"square"};if(g&&!f||!g&&!e)return i?"":j.path();switch(h=l[h]||"square",f=Math.round(f),e=Math.round(e),c=Math.round(c),d=Math.round(d),h){case"round":if(g)m=~~(e/2),m>f?(m=f,k=["M",c-~~(e/2),d,"l",0,0,"a",~~(e/2),m,0,0,1,e,0,"l",0,0,"z"]):k=["M",c-m,d,"l",0,m-f,"a",m,m,0,1,1,e,0,"l",0,f-m,"z"];else{var m=~~(f/2);m>e?(m=e,k=["M",c+.5,d+.5-~~(f/2),"l",0,0,"a",m,~~(f/2),0,0,1,0,f,"l",0,0,"z"]):k=["M",c+.5,d+.5-m,"l",e-m,0,"a",m,m,0,1,1,0,f,"l",m-e,0,"z"]}break;case"sharp":if(g)n=~~(e/2),k=["M",c+n,d,"l",-e,0,0,-b(f-n,0),n,-a(n,f),n,a(n,f),n,"z"];else{var n=~~(f/2);k=["M",c,d+n,"l",0,-f,b(e-n,0),0,a(n,e),n,-a(n,e),n+(f>2*n),"z"]}break;case"square":k=g?["M",c+~~(e/2),d,"l",1-e,0,0,-f,e-1,0,"z"]:["M",c,d+~~(f/2),"l",0,-f,e,0,0,f,"z"];break;case"soft":g?(m=a(Math.round(e/5),f),k=["M",c-~~(e/2),d,"l",0,m-f,"a",m,m,0,0,1,m,-m,"l",e-2*m,0,"a",m,m,0,0,1,m,m,"l",0,f-m,"z"]):(m=a(e,Math.round(f/5)),k=["M",c+.5,d+.5-~~(f/2),"l",e-m,0,"a",m,m,0,0,1,m,m,"l",0,f-2*m,"a",m,m,0,0,1,-m,m,"l",m-e,0,"z"])}return i?k.join(","):j.path(k)}function d(a,b,d,e,f,g,h){h=h||{};var i=this,j=h.type||"square",k=parseFloat(h.gutter||"20%"),l=a.set(),m=a.set(),n=a.set(),o=a.set(),p=Math.max.apply(Math,g),q=[],r=0,s=h.colors||i.colors,t=g.length;if(Raphael.is(g[0],"array")){p=[],r=t,t=0;for(var u=g.length;u--;)m.push(a.set()),p.push(Math.max.apply(Math,g[u])),t=Math.max(t,g[u].length);if(h.stacked)for(var u=t;u--;){for(var v=0,w=g.length;w--;)v+=+g[w][u]||0;q.push(v)}for(var u=g.length;u--;)if(t>g[u].length)for(var w=t;w--;)g[u].push(0);p=Math.max.apply(Math,h.stacked?q:p)}p=h.to||p;var x=100*(e/(t*(100+k)+k)),y=x*k/100,z=null==h.vgutter?20:h.vgutter,A=[],B=b+y,C=(f-2*z)/p;h.stretch||(y=Math.round(y),x=Math.floor(x)),!h.stacked&&(x/=r||1);for(var u=0;t>u;u++){A=[];for(var w=0;(r||1)>w;w++){var D=Math.round((r?g[w][u]:g[u])*C),E=d+f-z-D,F=c(Math.round(B+x/2),E+D,x,D,!0,j,null,a).attr({stroke:"none",fill:s[r?w:u]});r?m[w].push(F):m.push(F),F.y=E,F.x=Math.round(B+x/2),F.w=x,F.h=D,F.value=r?g[w][u]:g[u],h.stacked?A.push(F):B+=x}if(h.stacked){var G;o.push(G=a.rect(A[0].x-A[0].w/2,d,x,f).attr(i.shim)),G.bars=a.set();for(var H=0,I=A.length;I--;)A[I].toFront();for(var I=0,J=A.length;J>I;I++){var K,F=A[I],D=(H+F.value)*C,L=c(F.x,d+f-z-.5*!!H,x,D,!0,j,1,a);G.bars.push(F),H&&F.attr({path:L}),F.h=D,F.y=d+f-z-.5*!!H-D,n.push(K=a.rect(F.x-F.w/2,F.y,x,F.value*C).attr(i.shim)),K.bar=F,K.value=F.value,H+=F.value}B+=x}B+=y}if(o.toFront(),B=b+y,!h.stacked)for(var u=0;t>u;u++){for(var w=0;(r||1)>w;w++){var K;n.push(K=a.rect(Math.round(B),d+z,x,f-z).attr(i.shim)),K.bar=r?m[w][u]:m[u],K.value=K.bar.value,B+=x}B+=y}return l.label=function(b,c){b=b||[],this.labels=a.set();var e,j=-1/0;if(h.stacked){for(var k=0;t>k;k++)for(var l=0,o=0;(r||1)>o;o++)if(l+=r?g[o][k]:g[k],o==r-1){var q=i.labelise(b[k],l,p);e=a.text(m[o][k].x,d+f-z/2,q).attr(i.txtattr).attr({fill:h.legendcolor||"#000","text-anchor":"start"}).insertBefore(n[k*(r||1)+o]);var s=e.getBBox();j>s.x-7?e.remove():(this.labels.push(e),j=s.x+s.width)}}else for(var k=0;t>k;k++)for(var o=0;(r||1)>o;o++){var q=i.labelise(r?b[o]&&b[o][k]:b[k],r?g[o][k]:g[k],p);e=a.text(m[o][k].x-x/2,c?d+f-z/2:m[o][k].y-10,q).attr(i.txtattr).attr({fill:h.legendcolor||"#000","text-anchor":"start"}).insertBefore(n[k*(r||1)+o]);var s=e.getBBox();e.translate((x-s.width)/2,1),j>s.x-7?e.remove():(this.labels.push(e),j=s.x+s.width)}return this},l.hover=function(a,b){return o.hide(),n.show(),n.mouseover(a).mouseout(b),this},l.hoverColumn=function(a,b){return n.hide(),o.show(),b=b||function(){},o.mouseover(a).mouseout(b),this},l.click=function(a){return o.hide(),n.show(),n.click(a),this},l.each=function(a){if(!Raphael.is(a,"function"))return this;for(var b=n.length;b--;)a.call(n[b]);return this},l.eachColumn=function(a){if(!Raphael.is(a,"function"))return this;for(var b=o.length;b--;)a.call(o[b]);return this},l.clickColumn=function(a){return n.hide(),o.show(),o.click(a),this},l.push(m,n,o),l.bars=m,l.covers=n,l}function e(a,b,d,e,f,g,h){h=h||{};var i=this,j=h.type||"square",k=parseFloat(h.gutter||"20%"),l=a.set(),m=a.set(),n=a.set(),o=a.set(),p=Math.max.apply(Math,g),q=[],r=0,s=h.colors||i.colors,t=g.length;if(Raphael.is(g[0],"array")){p=[],r=t,t=0;for(var u=g.length;u--;)m.push(a.set()),p.push(Math.max.apply(Math,g[u])),t=Math.max(t,g[u].length);if(h.stacked)for(var u=t;u--;){for(var v=0,w=g.length;w--;)v+=+g[w][u]||0;q.push(v)}for(var u=g.length;u--;)if(t>g[u].length)for(var w=t;w--;)g[u].push(0);p=Math.max.apply(Math,h.stacked?q:p)}p=h.to||p;var x=Math.floor(100*(f/(t*(100+k)+k))),y=Math.floor(x*k/100),z=[],A=d+y,B=(e-1)/p;!h.stacked&&(x/=r||1);for(var u=0;t>u;u++){z=[];for(var w=0;(r||1)>w;w++){var C=r?g[w][u]:g[u],D=c(b,A+x/2,Math.round(C*B),x-1,!1,j,null,a).attr({stroke:"none",fill:s[r?w:u]});r?m[w].push(D):m.push(D),D.x=b+Math.round(C*B),D.y=A+x/2,D.w=Math.round(C*B),D.h=x,D.value=+C,h.stacked?z.push(D):A+=x}if(h.stacked){var E=a.rect(b,z[0].y-z[0].h/2,e,x).attr(i.shim);o.push(E),E.bars=a.set();for(var F=0,G=z.length;G--;)z[G].toFront();for(var G=0,H=z.length;H>G;G++){var I,D=z[G],C=Math.round((F+D.value)*B),J=c(b,D.y,C,x-1,!1,j,1,a);E.bars.push(D),F&&D.attr({path:J}),D.w=C,D.x=b+C,n.push(I=a.rect(b+F*B,D.y-D.h/2,D.value*B,x).attr(i.shim)),I.bar=D,F+=D.value}A+=x}A+=y}if(o.toFront(),A=d+y,!h.stacked)for(var u=0;t>u;u++){for(var w=0;(r||1)>w;w++){var I=a.rect(b,A,e,x).attr(i.shim);n.push(I),I.bar=r?m[w][u]:m[u],I.value=I.bar.value,A+=x}A+=y}return l.label=function(c,d){c=c||[],this.labels=a.set();for(var e=0;t>e;e++)for(var f=0;r>f;f++){var o,j=i.labelise(r?c[f]&&c[f][e]:c[e],r?g[f][e]:g[e],p),k=d?m[f][e].x-x/2+3:b+5;this.labels.push(o=a.text(k,m[f][e].y,j).attr(i.txtattr).attr({fill:h.legendcolor||"#000","text-anchor":"start"}).insertBefore(n[0])),b+5>o.getBBox().x?o.attr({x:b+5,"text-anchor":"start"}):m[f][e].label=o}return this},l.hover=function(a,b){return o.hide(),n.show(),b=b||function(){},n.mouseover(a).mouseout(b),this},l.hoverColumn=function(a,b){return n.hide(),o.show(),b=b||function(){},o.mouseover(a).mouseout(b),this},l.each=function(a){if(!Raphael.is(a,"function"))return this;for(var b=n.length;b--;)a.call(n[b]);return this},l.eachColumn=function(a){if(!Raphael.is(a,"function"))return this;for(var b=o.length;b--;)a.call(o[b]);return this},l.click=function(a){return o.hide(),n.show(),n.click(a),this},l.clickColumn=function(a){return n.hide(),o.show(),o.click(a),this},l.push(m,n,o),l.bars=m,l.covers=n,l}var a=Math.min,b=Math.max,f=function(){};f.prototype=Raphael.g,e.prototype=d.prototype=new f,Raphael.fn.hbarchart=function(a,b,c,d,f,g){return new e(this,a,b,c,d,f,g)},Raphael.fn.barchart=function(a,b,c,e,f,g){return new d(this,a,b,c,e,f,g)}})();
\ No newline at end of file
diff --git a/vendor/assets/javascripts/g.bar.js b/vendor/assets/javascripts/g.bar.js
new file mode 100644
index 0000000000000000000000000000000000000000..166bd654d6eec435e5990baaf70e6f477e16e5e5
--- /dev/null
+++ b/vendor/assets/javascripts/g.bar.js
@@ -0,0 +1,674 @@
+/*!
+ * g.Raphael 0.51 - Charting library, based on Raphaël
+ *
+ * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+(function () {
+    var mmin = Math.min,
+        mmax = Math.max;
+
+    function finger(x, y, width, height, dir, ending, isPath, paper) {
+        var path,
+            ends = { round: 'round', sharp: 'sharp', soft: 'soft', square: 'square' };
+
+        // dir 0 for horizontal and 1 for vertical
+        if ((dir && !height) || (!dir && !width)) {
+            return isPath ? "" : paper.path();
+        }
+
+        ending = ends[ending] || "square";
+        height = Math.round(height);
+        width = Math.round(width);
+        x = Math.round(x);
+        y = Math.round(y);
+
+        switch (ending) {
+            case "round":
+                if (!dir) {
+                    var r = ~~(height / 2);
+
+                    if (width < r) {
+                        r = width;
+                        path = [
+                            "M", x + .5, y + .5 - ~~(height / 2),
+                            "l", 0, 0,
+                            "a", r, ~~(height / 2), 0, 0, 1, 0, height,
+                            "l", 0, 0,
+                            "z"
+                        ];
+                    } else {
+                        path = [
+                            "M", x + .5, y + .5 - r,
+                            "l", width - r, 0,
+                            "a", r, r, 0, 1, 1, 0, height,
+                            "l", r - width, 0,
+                            "z"
+                        ];
+                    }
+                } else {
+                    r = ~~(width / 2);
+
+                    if (height < r) {
+                        r = height;
+                        path = [
+                            "M", x - ~~(width / 2), y,
+                            "l", 0, 0,
+                            "a", ~~(width / 2), r, 0, 0, 1, width, 0,
+                            "l", 0, 0,
+                            "z"
+                        ];
+                    } else {
+                        path = [
+                            "M", x - r, y,
+                            "l", 0, r - height,
+                            "a", r, r, 0, 1, 1, width, 0,
+                            "l", 0, height - r,
+                            "z"
+                        ];
+                    }
+                }
+                break;
+            case "sharp":
+                if (!dir) {
+                    var half = ~~(height / 2);
+
+                    path = [
+                        "M", x, y + half,
+                        "l", 0, -height, mmax(width - half, 0), 0, mmin(half, width), half, -mmin(half, width), half + (half * 2 < height),
+                        "z"
+                    ];
+                } else {
+                    half = ~~(width / 2);
+                    path = [
+                        "M", x + half, y,
+                        "l", -width, 0, 0, -mmax(height - half, 0), half, -mmin(half, height), half, mmin(half, height), half,
+                        "z"
+                    ];
+                }
+                break;
+            case "square":
+                if (!dir) {
+                    path = [
+                        "M", x, y + ~~(height / 2),
+                        "l", 0, -height, width, 0, 0, height,
+                        "z"
+                    ];
+                } else {
+                    path = [
+                        "M", x + ~~(width / 2), y,
+                        "l", 1 - width, 0, 0, -height, width - 1, 0,
+                        "z"
+                    ];
+                }
+                break;
+            case "soft":
+                if (!dir) {
+                    r = mmin(width, Math.round(height / 5));
+                    path = [
+                        "M", x + .5, y + .5 - ~~(height / 2),
+                        "l", width - r, 0,
+                        "a", r, r, 0, 0, 1, r, r,
+                        "l", 0, height - r * 2,
+                        "a", r, r, 0, 0, 1, -r, r,
+                        "l", r - width, 0,
+                        "z"
+                    ];
+                } else {
+                    r = mmin(Math.round(width / 5), height);
+                    path = [
+                        "M", x - ~~(width / 2), y,
+                        "l", 0, r - height,
+                        "a", r, r, 0, 0, 1, r, -r,
+                        "l", width - 2 * r, 0,
+                        "a", r, r, 0, 0, 1, r, r,
+                        "l", 0, height - r,
+                        "z"
+                    ];
+                }
+        }
+
+        if (isPath) {
+            return path.join(",");
+        } else {
+            return paper.path(path);
+        }
+    }
+
+/*\
+ * Paper.vbarchart
+ [ method ]
+ **
+ * Creates a vertical bar chart
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the chart
+ - y (number) y coordinate of the chart
+ - width (number) width of the chart (respected by all elements in the set)
+ - height (number) height of the chart (respected by all elements in the set)
+ - values (array) values
+ - opts (object) options for the chart
+ o {
+ o type (string) type of endings of the bar. Default: 'square'. Other options are: 'round', 'sharp', 'soft'.
+ o gutter (number)(string) default '20%' (WHAT DOES IT DO?)
+ o vgutter (number)
+ o colors (array) colors be used repeatedly to plot the bars. If multicolumn bar is used each sequence of bars with use a different color.
+ o stacked (boolean) whether or not to tread values as in a stacked bar chart
+ o to
+ o stretch (boolean)
+ o }
+ **
+ = (object) path element of the popup
+ > Usage
+ | r.vbarchart(0, 0, 620, 260, [76, 70, 67, 71, 69], {})
+ \*/
+ 
+    function VBarchart(paper, x, y, width, height, values, opts) {
+        opts = opts || {};
+
+        var chartinst = this,
+            type = opts.type || "square",
+            gutter = parseFloat(opts.gutter || "20%"),
+            chart = paper.set(),
+            bars = paper.set(),
+            covers = paper.set(),
+            covers2 = paper.set(),
+            total = Math.max.apply(Math, values),
+            stacktotal = [],
+            multi = 0,
+            colors = opts.colors || chartinst.colors,
+            len = values.length;
+
+        if (Raphael.is(values[0], "array")) {
+            total = [];
+            multi = len;
+            len = 0;
+
+            for (var i = values.length; i--;) {
+                bars.push(paper.set());
+                total.push(Math.max.apply(Math, values[i]));
+                len = Math.max(len, values[i].length);
+            }
+
+            if (opts.stacked) {
+                for (var i = len; i--;) {
+                    var tot = 0;
+
+                    for (var j = values.length; j--;) {
+                        tot +=+ values[j][i] || 0;
+                    }
+
+                    stacktotal.push(tot);
+                }
+            }
+
+            for (var i = values.length; i--;) {
+                if (values[i].length < len) {
+                    for (var j = len; j--;) {
+                        values[i].push(0);
+                    }
+                }
+            }
+
+            total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
+        }
+        
+        total = (opts.to) || total;
+
+        var barwidth = width / (len * (100 + gutter) + gutter) * 100,
+            barhgutter = barwidth * gutter / 100,
+            barvgutter = opts.vgutter == null ? 20 : opts.vgutter,
+            stack = [],
+            X = x + barhgutter,
+            Y = (height - 2 * barvgutter) / total;
+
+        if (!opts.stretch) {
+            barhgutter = Math.round(barhgutter);
+            barwidth = Math.floor(barwidth);
+        }
+
+        !opts.stacked && (barwidth /= multi || 1);
+
+        for (var i = 0; i < len; i++) {
+            stack = [];
+
+            for (var j = 0; j < (multi || 1); j++) {
+                var h = Math.round((multi ? values[j][i] : values[i]) * Y),
+                    top = y + height - barvgutter - h,
+                    bar = finger(Math.round(X + barwidth / 2), top + h, barwidth, h, true, type, null, paper).attr({ stroke: "none", fill: colors[multi ? j : i] });
+
+                if (multi) {
+                    bars[j].push(bar);
+                } else {
+                    bars.push(bar);
+                }
+
+                bar.y = top;
+                bar.x = Math.round(X + barwidth / 2);
+                bar.w = barwidth;
+                bar.h = h;
+                bar.value = multi ? values[j][i] : values[i];
+
+                if (!opts.stacked) {
+                    X += barwidth;
+                } else {
+                    stack.push(bar);
+                }
+            }
+
+            if (opts.stacked) {
+                var cvr;
+
+                covers2.push(cvr = paper.rect(stack[0].x - stack[0].w / 2, y, barwidth, height).attr(chartinst.shim));
+                cvr.bars = paper.set();
+
+                var size = 0;
+
+                for (var s = stack.length; s--;) {
+                    stack[s].toFront();
+                }
+
+                for (var s = 0, ss = stack.length; s < ss; s++) {
+                    var bar = stack[s],
+                        cover,
+                        h = (size + bar.value) * Y,
+                        path = finger(bar.x, y + height - barvgutter - !!size * .5, barwidth, h, true, type, 1, paper);
+
+                    cvr.bars.push(bar);
+                    size && bar.attr({path: path});
+                    bar.h = h;
+                    bar.y = y + height - barvgutter - !!size * .5 - h;
+                    covers.push(cover = paper.rect(bar.x - bar.w / 2, bar.y, barwidth, bar.value * Y).attr(chartinst.shim));
+                    cover.bar = bar;
+                    cover.value = bar.value;
+                    size += bar.value;
+                }
+
+                X += barwidth;
+            }
+
+            X += barhgutter;
+        }
+
+        covers2.toFront();
+        X = x + barhgutter;
+
+        if (!opts.stacked) {
+            for (var i = 0; i < len; i++) {
+                for (var j = 0; j < (multi || 1); j++) {
+                    var cover;
+
+                    covers.push(cover = paper.rect(Math.round(X), y + barvgutter, barwidth, height - barvgutter).attr(chartinst.shim));
+                    cover.bar = multi ? bars[j][i] : bars[i];
+                    cover.value = cover.bar.value;
+                    X += barwidth;
+                }
+
+                X += barhgutter;
+            }
+        }
+
+        chart.label = function (labels, isBottom) {
+            labels = labels || [];
+            this.labels = paper.set();
+
+            var L, l = -Infinity;
+
+            if (opts.stacked) {
+                for (var i = 0; i < len; i++) {
+                    var tot = 0;
+
+                    for (var j = 0; j < (multi || 1); j++) {
+                        tot += multi ? values[j][i] : values[i];
+
+                        if (j == multi - 1) {
+                            var label = paper.labelise(labels[i], tot, total);
+
+                            L = paper.text(bars[i * (multi || 1) + j].x, y + height - barvgutter / 2, label).attr(txtattr).insertBefore(covers[i * (multi || 1) + j]);
+
+                            var bb = L.getBBox();
+
+                            if (bb.x - 7 < l) {
+                                L.remove();
+                            } else {
+                                this.labels.push(L);
+                                l = bb.x + bb.width;
+                            }
+                        }
+                    }
+                }
+            } else {
+                for (var i = 0; i < len; i++) {
+                    for (var j = 0; j < (multi || 1); j++) {
+                        var label = paper.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total);
+
+                        L = paper.text(bars[i * (multi || 1) + j].x, isBottom ? y + height - barvgutter / 2 : bars[i * (multi || 1) + j].y - 10, label).attr(txtattr).insertBefore(covers[i * (multi || 1) + j]);
+
+                        var bb = L.getBBox();
+
+                        if (bb.x - 7 < l) {
+                            L.remove();
+                        } else {
+                            this.labels.push(L);
+                            l = bb.x + bb.width;
+                        }
+                    }
+                }
+            }
+            return this;
+        };
+
+        chart.hover = function (fin, fout) {
+            covers2.hide();
+            covers.show();
+            covers.mouseover(fin).mouseout(fout);
+            return this;
+        };
+
+        chart.hoverColumn = function (fin, fout) {
+            covers.hide();
+            covers2.show();
+            fout = fout || function () {};
+            covers2.mouseover(fin).mouseout(fout);
+            return this;
+        };
+
+        chart.click = function (f) {
+            covers2.hide();
+            covers.show();
+            covers.click(f);
+            return this;
+        };
+
+        chart.each = function (f) {
+            if (!Raphael.is(f, "function")) {
+                return this;
+            }
+            for (var i = covers.length; i--;) {
+                f.call(covers[i]);
+            }
+            return this;
+        };
+
+        chart.eachColumn = function (f) {
+            if (!Raphael.is(f, "function")) {
+                return this;
+            }
+            for (var i = covers2.length; i--;) {
+                f.call(covers2[i]);
+            }
+            return this;
+        };
+
+        chart.clickColumn = function (f) {
+            covers.hide();
+            covers2.show();
+            covers2.click(f);
+            return this;
+        };
+
+        chart.push(bars, covers, covers2);
+        chart.bars = bars;
+        chart.covers = covers;
+        return chart;
+    };
+    
+    //inheritance
+    var F = function() {};
+    F.prototype = Raphael.g;
+    HBarchart.prototype = VBarchart.prototype = new F; //prototype reused by hbarchart
+    
+    Raphael.fn.barchart = function(x, y, width, height, values, opts) {
+        return new VBarchart(this, x, y, width, height, values, opts);
+    };
+    
+/*\
+ * Paper.barchart
+ [ method ]
+ **
+ * Creates a horizontal bar chart
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the chart
+ - y (number) y coordinate of the chart
+ - width (number) width of the chart (respected by all elements in the set)
+ - height (number) height of the chart (respected by all elements in the set)
+ - values (array) values
+ - opts (object) options for the chart
+ o {
+ o type (string) type of endings of the bar. Default: 'square'. Other options are: 'round', 'sharp', 'soft'.
+ o gutter (number)(string) default '20%' (WHAT DOES IT DO?)
+ o vgutter (number)
+ o colors (array) colors be used repeatedly to plot the bars. If multicolumn bar is used each sequence of bars with use a different color.
+ o stacked (boolean) whether or not to tread values as in a stacked bar chart
+ o to
+ o stretch (boolean)
+ o }
+ **
+ = (object) path element of the popup
+ > Usage
+ | r.barchart(0, 0, 620, 260, [76, 70, 67, 71, 69], {})
+ \*/
+ 
+    function HBarchart(paper, x, y, width, height, values, opts) {
+        opts = opts || {};
+
+        var chartinst = this,
+            type = opts.type || "square",
+            gutter = parseFloat(opts.gutter || "20%"),
+            chart = paper.set(),
+            bars = paper.set(),
+            covers = paper.set(),
+            covers2 = paper.set(),
+            total = Math.max.apply(Math, values),
+            stacktotal = [],
+            multi = 0,
+            colors = opts.colors || chartinst.colors,
+            len = values.length;
+
+        if (Raphael.is(values[0], "array")) {
+            total = [];
+            multi = len;
+            len = 0;
+
+            for (var i = values.length; i--;) {
+                bars.push(paper.set());
+                total.push(Math.max.apply(Math, values[i]));
+                len = Math.max(len, values[i].length);
+            }
+
+            if (opts.stacked) {
+                for (var i = len; i--;) {
+                    var tot = 0;
+                    for (var j = values.length; j--;) {
+                        tot +=+ values[j][i] || 0;
+                    }
+                    stacktotal.push(tot);
+                }
+            }
+
+            for (var i = values.length; i--;) {
+                if (values[i].length < len) {
+                    for (var j = len; j--;) {
+                        values[i].push(0);
+                    }
+                }
+            }
+
+            total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
+        }
+        
+        total = (opts.to) || total;
+
+        var barheight = Math.floor(height / (len * (100 + gutter) + gutter) * 100),
+            bargutter = Math.floor(barheight * gutter / 100),
+            stack = [],
+            Y = y + bargutter,
+            X = (width - 1) / total;
+
+        !opts.stacked && (barheight /= multi || 1);
+
+        for (var i = 0; i < len; i++) {
+            stack = [];
+
+            for (var j = 0; j < (multi || 1); j++) {
+                var val = multi ? values[j][i] : values[i],
+                    bar = finger(x, Y + barheight / 2, Math.round(val * X), barheight - 1, false, type, null, paper).attr({stroke: "none", fill: colors[multi ? j : i]});
+
+                if (multi) {
+                    bars[j].push(bar);
+                } else {
+                    bars.push(bar);
+                }
+
+                bar.x = x + Math.round(val * X);
+                bar.y = Y + barheight / 2;
+                bar.w = Math.round(val * X);
+                bar.h = barheight;
+                bar.value = +val;
+
+                if (!opts.stacked) {
+                    Y += barheight;
+                } else {
+                    stack.push(bar);
+                }
+            }
+
+            if (opts.stacked) {
+                var cvr = paper.rect(x, stack[0].y - stack[0].h / 2, width, barheight).attr(chartinst.shim);
+
+                covers2.push(cvr);
+                cvr.bars = paper.set();
+
+                var size = 0;
+
+                for (var s = stack.length; s--;) {
+                    stack[s].toFront();
+                }
+
+                for (var s = 0, ss = stack.length; s < ss; s++) {
+                    var bar = stack[s],
+                        cover,
+                        val = Math.round((size + bar.value) * X),
+                        path = finger(x, bar.y, val, barheight - 1, false, type, 1, paper);
+
+                    cvr.bars.push(bar);
+                    size && bar.attr({ path: path });
+                    bar.w = val;
+                    bar.x = x + val;
+                    covers.push(cover = paper.rect(x + size * X, bar.y - bar.h / 2, bar.value * X, barheight).attr(chartinst.shim));
+                    cover.bar = bar;
+                    size += bar.value;
+                }
+
+                Y += barheight;
+            }
+
+            Y += bargutter;
+        }
+
+        covers2.toFront();
+        Y = y + bargutter;
+
+        if (!opts.stacked) {
+            for (var i = 0; i < len; i++) {
+                for (var j = 0; j < (multi || 1); j++) {
+                    var cover = paper.rect(x, Y, width, barheight).attr(chartinst.shim);
+
+                    covers.push(cover);
+                    cover.bar = multi ? bars[j][i] : bars[i];
+                    cover.value = cover.bar.value;
+                    Y += barheight;
+                }
+
+                Y += bargutter;
+            }
+        }
+
+        chart.label = function (labels, isRight) {
+            labels = labels || [];
+            this.labels = paper.set();
+
+            for (var i = 0; i < len; i++) {
+                for (var j = 0; j < multi; j++) {
+                    var  label = paper.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total),
+                        X = isRight ? bars[i * (multi || 1) + j].x - barheight / 2 + 3 : x + 5,
+                        A = isRight ? "end" : "start",
+                        L;
+
+                    this.labels.push(L = paper.text(X, bars[i * (multi || 1) + j].y, label).attr(txtattr).attr({ "text-anchor": A }).insertBefore(covers[0]));
+
+                    if (L.getBBox().x < x + 5) {
+                        L.attr({x: x + 5, "text-anchor": "start"});
+                    } else {
+                        bars[i * (multi || 1) + j].label = L;
+                    }
+                }
+            }
+
+            return this;
+        };
+
+        chart.hover = function (fin, fout) {
+            covers2.hide();
+            covers.show();
+            fout = fout || function () {};
+            covers.mouseover(fin).mouseout(fout);
+            return this;
+        };
+
+        chart.hoverColumn = function (fin, fout) {
+            covers.hide();
+            covers2.show();
+            fout = fout || function () {};
+            covers2.mouseover(fin).mouseout(fout);
+            return this;
+        };
+
+        chart.each = function (f) {
+            if (!Raphael.is(f, "function")) {
+                return this;
+            }
+            for (var i = covers.length; i--;) {
+                f.call(covers[i]);
+            }
+            return this;
+        };
+
+        chart.eachColumn = function (f) {
+            if (!Raphael.is(f, "function")) {
+                return this;
+            }
+            for (var i = covers2.length; i--;) {
+                f.call(covers2[i]);
+            }
+            return this;
+        };
+
+        chart.click = function (f) {
+            covers2.hide();
+            covers.show();
+            covers.click(f);
+            return this;
+        };
+
+        chart.clickColumn = function (f) {
+            covers.hide();
+            covers2.show();
+            covers2.click(f);
+            return this;
+        };
+
+        chart.push(bars, covers, covers2);
+        chart.bars = bars;
+        chart.covers = covers;
+        return chart;
+    };
+    
+    Raphael.fn.hbarchart = function(x, y, width, height, values, opts) {
+        return new HBarchart(this, x, y, width, height, values, opts);
+    };
+    
+})();
diff --git a/vendor/assets/javascripts/g.raphael-min.js b/vendor/assets/javascripts/g.raphael-min.js
deleted file mode 100644
index f8b381c623bc553a4cefcf930b8bf08078ad4847..0000000000000000000000000000000000000000
--- a/vendor/assets/javascripts/g.raphael-min.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * g.Raphael 0.51 - Charting library, based on Raphaël
- *
- * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
- * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
- */
-Raphael.el.popup=function(a,b,c,d){var f,g,h,i,j,e=this.paper||this[0].paper;if(e){switch(this.type){case"text":case"circle":case"ellipse":h=!0;break;default:h=!1}a=null==a?"up":a,b=b||5,f=this.getBBox(),c="number"==typeof c?c:h?f.x+f.width/2:f.x,d="number"==typeof d?d:h?f.y+f.height/2:f.y,i=Math.max(f.width/2-b,0),j=Math.max(f.height/2-b,0),this.translate(c-f.x-(h?f.width/2:0),d-f.y-(h?f.height/2:0)),f=this.getBBox();var k={up:["M",c,d,"l",-b,-b,-i,0,"a",b,b,0,0,1,-b,-b,"l",0,-f.height,"a",b,b,0,0,1,b,-b,"l",2*b+2*i,0,"a",b,b,0,0,1,b,b,"l",0,f.height,"a",b,b,0,0,1,-b,b,"l",-i,0,"z"].join(","),down:["M",c,d,"l",b,b,i,0,"a",b,b,0,0,1,b,b,"l",0,f.height,"a",b,b,0,0,1,-b,b,"l",-(2*b+2*i),0,"a",b,b,0,0,1,-b,-b,"l",0,-f.height,"a",b,b,0,0,1,b,-b,"l",i,0,"z"].join(","),left:["M",c,d,"l",-b,b,0,j,"a",b,b,0,0,1,-b,b,"l",-f.width,0,"a",b,b,0,0,1,-b,-b,"l",0,-(2*b+2*j),"a",b,b,0,0,1,b,-b,"l",f.width,0,"a",b,b,0,0,1,b,b,"l",0,j,"z"].join(","),right:["M",c,d,"l",b,-b,0,-j,"a",b,b,0,0,1,b,-b,"l",f.width,0,"a",b,b,0,0,1,b,b,"l",0,2*b+2*j,"a",b,b,0,0,1,-b,b,"l",-f.width,0,"a",b,b,0,0,1,-b,-b,"l",0,-j,"z"].join(",")};return g={up:{x:-!h*(f.width/2),y:2*-b-(h?f.height/2:f.height)},down:{x:-!h*(f.width/2),y:2*b+(h?f.height/2:f.height)},left:{x:2*-b-(h?f.width/2:f.width),y:-!h*(f.height/2)},right:{x:2*b+(h?f.width/2:f.width),y:-!h*(f.height/2)}}[a],this.translate(g.x,g.y),e.path(k[a]).attr({fill:"#000",stroke:"none"}).insertBefore(this.node?this:this[0])}},Raphael.el.tag=function(a,b,c,d){var e=3,f=this.paper||this[0].paper;if(f){var i,j,k,g=f.path().attr({fill:"#000",stroke:"#000"}),h=this.getBBox();switch(this.type){case"text":case"circle":case"ellipse":k=!0;break;default:k=!1}return a=a||0,c="number"==typeof c?c:k?h.x+h.width/2:h.x,d="number"==typeof d?d:k?h.y+h.height/2:h.y,b=null==b?5:b,j=.5522*b,h.height>=2*b?g.attr({path:["M",c,d+b,"a",b,b,0,1,1,0,2*-b,b,b,0,1,1,0,2*b,"m",0,2*-b-e,"a",b+e,b+e,0,1,0,0,2*(b+e),"L",c+b+e,d+h.height/2+e,"l",h.width+2*e,0,0,-h.height-2*e,-h.width-2*e,0,"L",c,d-b-e].join(",")}):(i=Math.sqrt(Math.pow(b+e,2)-Math.pow(h.height/2+e,2)),g.attr({path:["M",c,d+b,"c",-j,0,-b,j-b,-b,-b,0,-j,b-j,-b,b,-b,j,0,b,b-j,b,b,0,j,j-b,b,-b,b,"M",c+i,d-h.height/2-e,"a",b+e,b+e,0,1,0,0,h.height+2*e,"l",b+e-i+h.width+2*e,0,0,-h.height-2*e,"L",c+i,d-h.height/2-e].join(",")})),a=360-a,g.rotate(a,c,d),this.attrs?(this.attr(this.attrs.x?"x":"cx",c+b+e+(k?h.width/2:"text"==this.type?h.width:0)).attr("y",k?d:d-h.height/2),this.rotate(a,c,d),a>90&&270>a&&this.attr(this.attrs.x?"x":"cx",c-b-e-(k?h.width/2:h.width)).rotate(180,c,d)):a>90&&270>a?(this.translate(c-h.x-h.width-b-e,d-h.y-h.height/2),this.rotate(a-180,h.x+h.width+b+e,h.y+h.height/2)):(this.translate(c-h.x+b+e,d-h.y-h.height/2),this.rotate(a,h.x-b-e,h.y+h.height/2)),g.insertBefore(this.node?this:this[0])}},Raphael.el.drop=function(a,b,c){var f,g,h,i,j,d=this.getBBox(),e=this.paper||this[0].paper;if(e){switch(this.type){case"text":case"circle":case"ellipse":f=!0;break;default:f=!1}return a=a||0,b="number"==typeof b?b:f?d.x+d.width/2:d.x,c="number"==typeof c?c:f?d.y+d.height/2:d.y,g=Math.max(d.width,d.height)+Math.min(d.width,d.height),h=e.path(["M",b,c,"l",g,0,"A",.4*g,.4*g,0,1,0,b+.7*g,c-.7*g,"z"]).attr({fill:"#000",stroke:"none"}).rotate(22.5-a,b,c),a=(a+90)*Math.PI/180,i=b+g*Math.sin(a)-(f?0:d.width/2),j=c+g*Math.cos(a)-(f?0:d.height/2),this.attrs?this.attr(this.attrs.x?"x":"cx",i).attr(this.attrs.y?"y":"cy",j):this.translate(i-d.x,j-d.y),h.insertBefore(this.node?this:this[0])}},Raphael.el.flag=function(a,b,c){var d=3,e=this.paper||this[0].paper;if(e){var i,f=e.path().attr({fill:"#000",stroke:"#000"}),g=this.getBBox(),h=g.height/2;switch(this.type){case"text":case"circle":case"ellipse":i=!0;break;default:i=!1}return a=a||0,b="number"==typeof b?b:i?g.x+g.width/2:g.x,c="number"==typeof c?c:i?g.y+g.height/2:g.y,f.attr({path:["M",b,c,"l",h+d,-h-d,g.width+2*d,0,0,g.height+2*d,-g.width-2*d,0,"z"].join(",")}),a=360-a,f.rotate(a,b,c),this.attrs?(this.attr(this.attrs.x?"x":"cx",b+h+d+(i?g.width/2:"text"==this.type?g.width:0)).attr("y",i?c:c-g.height/2),this.rotate(a,b,c),a>90&&270>a&&this.attr(this.attrs.x?"x":"cx",b-h-d-(i?g.width/2:g.width)).rotate(180,b,c)):a>90&&270>a?(this.translate(b-g.x-g.width-h-d,c-g.y-g.height/2),this.rotate(a-180,g.x+g.width+h+d,g.y+g.height/2)):(this.translate(b-g.x+h+d,c-g.y-g.height/2),this.rotate(a,g.x-h-d,g.y+g.height/2)),f.insertBefore(this.node?this:this[0])}},Raphael.el.label=function(){var a=this.getBBox(),b=this.paper||this[0].paper,c=Math.min(20,a.width+10,a.height+10)/2;if(b)return b.rect(a.x-c/2,a.y-c/2,a.width+c,a.height+c,c).attr({stroke:"none",fill:"#000"}).insertBefore(this.node?this:this[0])},Raphael.el.blob=function(a,b,c){var g,h,i,d=this.getBBox(),e=Math.PI/180,f=this.paper||this[0].paper;if(f){switch(this.type){case"text":case"circle":case"ellipse":h=!0;break;default:h=!1}g=f.path().attr({fill:"#000",stroke:"none"}),a=(+a+1?a:45)+90,i=Math.min(d.height,d.width),b="number"==typeof b?b:h?d.x+d.width/2:d.x,c="number"==typeof c?c:h?d.y+d.height/2:d.y;var j=Math.max(d.width+i,25*i/12),k=Math.max(d.height+i,25*i/12),l=b+i*Math.sin((a-22.5)*e),m=c+i*Math.cos((a-22.5)*e),n=b+i*Math.sin((a+22.5)*e),o=c+i*Math.cos((a+22.5)*e),p=(n-l)/2,q=(o-m)/2,r=j/2,s=k/2,t=-Math.sqrt(Math.abs(r*r*s*s-r*r*q*q-s*s*p*p)/(r*r*q*q+s*s*p*p)),u=t*r*q/s+(n+l)/2,v=t*-s*p/r+(o+m)/2;return g.attr({x:u,y:v,path:["M",b,c,"L",n,o,"A",r,s,0,1,1,l,m,"z"].join(",")}),this.translate(u-d.x-d.width/2,v-d.y-d.height/2),g.insertBefore(this.node?this:this[0])}},Raphael.fn.label=function(a,b,c){var d=this.set();return c=this.text(a,b,c).attr(Raphael.g.txtattr),d.push(c.label(),c)},Raphael.fn.popup=function(a,b,c,d,e){var f=this.set();return c=this.text(a,b,c).attr(Raphael.g.txtattr),f.push(c.popup(d,e),c)},Raphael.fn.tag=function(a,b,c,d,e){var f=this.set();return c=this.text(a,b,c).attr(Raphael.g.txtattr),f.push(c.tag(d,e),c)},Raphael.fn.flag=function(a,b,c,d){var e=this.set();return c=this.text(a,b,c).attr(Raphael.g.txtattr),e.push(c.flag(d),c)},Raphael.fn.drop=function(a,b,c,d){var e=this.set();return c=this.text(a,b,c).attr(Raphael.g.txtattr),e.push(c.drop(d),c)},Raphael.fn.blob=function(a,b,c,d){var e=this.set();return c=this.text(a,b,c).attr(Raphael.g.txtattr),e.push(c.blob(d),c)},Raphael.el.lighter=function(a){a=a||2;var b=[this.attrs.fill,this.attrs.stroke];return this.fs=this.fs||[b[0],b[1]],b[0]=Raphael.rgb2hsb(Raphael.getRGB(b[0]).hex),b[1]=Raphael.rgb2hsb(Raphael.getRGB(b[1]).hex),b[0].b=Math.min(b[0].b*a,1),b[0].s=b[0].s/a,b[1].b=Math.min(b[1].b*a,1),b[1].s=b[1].s/a,this.attr({fill:"hsb("+[b[0].h,b[0].s,b[0].b]+")",stroke:"hsb("+[b[1].h,b[1].s,b[1].b]+")"}),this},Raphael.el.darker=function(a){a=a||2;var b=[this.attrs.fill,this.attrs.stroke];return this.fs=this.fs||[b[0],b[1]],b[0]=Raphael.rgb2hsb(Raphael.getRGB(b[0]).hex),b[1]=Raphael.rgb2hsb(Raphael.getRGB(b[1]).hex),b[0].s=Math.min(b[0].s*a,1),b[0].b=b[0].b/a,b[1].s=Math.min(b[1].s*a,1),b[1].b=b[1].b/a,this.attr({fill:"hsb("+[b[0].h,b[0].s,b[0].b]+")",stroke:"hsb("+[b[1].h,b[1].s,b[1].b]+")"}),this},Raphael.el.resetBrightness=function(){return this.fs&&(this.attr({fill:this.fs[0],stroke:this.fs[1]}),delete this.fs),this},function(){var a=["lighter","darker","resetBrightness"],b=["popup","tag","flag","label","drop","blob"];for(var c in b)(function(a){Raphael.st[a]=function(){return Raphael.el[a].apply(this,arguments)}})(b[c]);for(var c in a)(function(a){Raphael.st[a]=function(){for(var b=0;this.length>b;b++)this[b][a].apply(this[b],arguments);return this}})(a[c])}(),Raphael.g={shim:{stroke:"none",fill:"#000","fill-opacity":0},txtattr:{font:"12px Arial, sans-serif",fill:"#fff"},colors:function(){for(var a=[.6,.2,.05,.1333,.75,0],b=[],c=0;10>c;c++)a.length>c?b.push("hsb("+a[c]+",.75, .75)"):b.push("hsb("+a[c-a.length]+", 1, .5)");return b}(),snapEnds:function(a,b,c){function f(a){return.25>Math.abs(a-.5)?~~a+.5:Math.round(a)}var d=a,e=b;if(d==e)return{from:d,to:e,power:0};var g=(e-d)/c,h=~~g,i=h,j=0;if(h){for(;i;)j--,i=~~(g*Math.pow(10,j))/Math.pow(10,j);j++}else{if(0!=g&&isFinite(g))for(;!h;)j=j||1,h=~~(g*Math.pow(10,j))/Math.pow(10,j),j++;else j=1;j&&j--}return e=f(b*Math.pow(10,j))/Math.pow(10,j),b>e&&(e=f((b+.5)*Math.pow(10,j))/Math.pow(10,j)),d=f((a-(j>0?0:.5))*Math.pow(10,j))/Math.pow(10,j),{from:d,to:e,power:j}},axis:function(a,b,c,d,e,f,g,h,i,j,k){j=null==j?2:j,i=i||"t",f=f||10,k=arguments[arguments.length-1];var t,l="|"==i||" "==i?["M",a+.5,b,"l",0,.001]:1==g||3==g?["M",a+.5,b,"l",0,-c]:["M",a,b+.5,"l",c,0],m=this.snapEnds(d,e,f),n=m.from,o=m.to,p=m.power,q=0,r={font:"11px 'Fontin Sans', Fontin-Sans, sans-serif"},s=k.set();t=(o-n)/f;var u=n,v=p>0?p:0;if(z=c/f,1==+g||3==+g){for(var w=b,x=(g-1?1:-1)*(j+3+!!(g-1));w>=b-c;)"-"!=i&&" "!=i&&(l=l.concat(["M",a-("+"==i||"|"==i?j:2*!(g-1)*j),w+.5,"l",2*j+1,0])),s.push(k.text(a+x,w,h&&h[q++]||(Math.round(u)==u?u:+u.toFixed(v))).attr(r).attr({"text-anchor":g-1?"start":"end"})),u+=t,w-=z;Math.round(w+z-(b-c))&&("-"!=i&&" "!=i&&(l=l.concat(["M",a-("+"==i||"|"==i?j:2*!(g-1)*j),b-c+.5,"l",2*j+1,0])),s.push(k.text(a+x,b-c,h&&h[q]||(Math.round(u)==u?u:+u.toFixed(v))).attr(r).attr({"text-anchor":g-1?"start":"end"})))}else{u=n,v=(p>0)*p,x=(g?-1:1)*(j+9+!g);for(var y=a,z=c/f,A=0,B=0;a+c>=y;){"-"!=i&&" "!=i&&(l=l.concat(["M",y+.5,b-("+"==i?j:2*!!g*j),"l",0,2*j+1])),s.push(A=k.text(y,b+x,h&&h[q++]||(Math.round(u)==u?u:+u.toFixed(v))).attr(r));var C=A.getBBox();B>=C.x-5?s.pop(s.length-1).remove():B=C.x+C.width,u+=t,y+=z}Math.round(y-z-a-c)&&("-"!=i&&" "!=i&&(l=l.concat(["M",a+c+.5,b-("+"==i?j:2*!!g*j),"l",0,2*j+1])),s.push(k.text(a+c,b+x,h&&h[q]||(Math.round(u)==u?u:+u.toFixed(v))).attr(r)))}var D=k.path(l);return D.text=s,D.all=k.set([D,s]),D.remove=function(){this.text.remove(),this.constructor.prototype.remove.call(this)},D},labelise:function(a,b,c){return a?(a+"").replace(/(##+(?:\.#+)?)|(%%+(?:\.%+)?)/g,function(a,d,e){return d?(+b).toFixed(d.replace(/^#+\.?/g,"").length):e?(100*b/c).toFixed(e.replace(/^%+\.?/g,"").length)+"%":void 0}):(+b).toFixed(0)}};
\ No newline at end of file
diff --git a/vendor/assets/javascripts/g.raphael.js b/vendor/assets/javascripts/g.raphael.js
new file mode 100644
index 0000000000000000000000000000000000000000..27f27caf9f2c8321120f9ce263deb847f8f46971
--- /dev/null
+++ b/vendor/assets/javascripts/g.raphael.js
@@ -0,0 +1,861 @@
+/*!
+ * g.Raphael 0.51 - Charting library, based on Raphaël
+ *
+ * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+
+/*
+ * Tooltips on Element prototype
+ */
+/*\
+ * Element.popup
+ [ method ]
+ **
+ * Puts the context Element in a 'popup' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - dir (string) location of Element relative to the tail: `'down'`, `'left'`, `'up'` [default], or `'right'`.
+ - size (number) amount of bevel/padding around the Element, as well as half the width and height of the tail [default: `5`]
+ - x (number) x coordinate of the popup's tail [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the popup's tail [default: Element's `y` or `cy`]
+ **
+ = (object) path element of the popup
+ \*/
+Raphael.el.popup = function (dir, size, x, y) {
+    var paper = this.paper || this[0].paper,
+        bb, xy, center, cw, ch;
+
+    if (!paper) return;
+
+    switch (this.type) {
+        case 'text':
+        case 'circle':
+        case 'ellipse': center = true; break;
+        default: center = false;
+    }
+
+    dir = dir == null ? 'up' : dir;
+    size = size || 5;
+    bb = this.getBBox();
+
+    x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+    y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
+    cw = Math.max(bb.width / 2 - size, 0);
+    ch = Math.max(bb.height / 2 - size, 0);
+
+    this.translate(x - bb.x - (center ? bb.width / 2 : 0), y - bb.y - (center ? bb.height / 2 : 0));
+    bb = this.getBBox();
+
+    var paths = {
+        up: [
+            'M', x, y,
+            'l', -size, -size, -cw, 0,
+            'a', size, size, 0, 0, 1, -size, -size,
+            'l', 0, -bb.height,
+            'a', size, size, 0, 0, 1, size, -size,
+            'l', size * 2 + cw * 2, 0,
+            'a', size, size, 0, 0, 1, size, size,
+            'l', 0, bb.height,
+            'a', size, size, 0, 0, 1, -size, size,
+            'l', -cw, 0,
+            'z'
+        ].join(','),
+        down: [
+            'M', x, y,
+            'l', size, size, cw, 0,
+            'a', size, size, 0, 0, 1, size, size,
+            'l', 0, bb.height,
+            'a', size, size, 0, 0, 1, -size, size,
+            'l', -(size * 2 + cw * 2), 0,
+            'a', size, size, 0, 0, 1, -size, -size,
+            'l', 0, -bb.height,
+            'a', size, size, 0, 0, 1, size, -size,
+            'l', cw, 0,
+            'z'
+        ].join(','),
+        left: [
+            'M', x, y,
+            'l', -size, size, 0, ch,
+            'a', size, size, 0, 0, 1, -size, size,
+            'l', -bb.width, 0,
+            'a', size, size, 0, 0, 1, -size, -size,
+            'l', 0, -(size * 2 + ch * 2),
+            'a', size, size, 0, 0, 1, size, -size,
+            'l', bb.width, 0,
+            'a', size, size, 0, 0, 1, size, size,
+            'l', 0, ch,
+            'z'
+        ].join(','),
+        right: [
+            'M', x, y,
+            'l', size, -size, 0, -ch,
+            'a', size, size, 0, 0, 1, size, -size,
+            'l', bb.width, 0,
+            'a', size, size, 0, 0, 1, size, size,
+            'l', 0, size * 2 + ch * 2,
+            'a', size, size, 0, 0, 1, -size, size,
+            'l', -bb.width, 0,
+            'a', size, size, 0, 0, 1, -size, -size,
+            'l', 0, -ch,
+            'z'
+        ].join(',')
+    };
+
+    xy = {
+        up: { x: -!center * (bb.width / 2), y: -size * 2 - (center ? bb.height / 2 : bb.height) },
+        down: { x: -!center * (bb.width / 2), y: size * 2 + (center ? bb.height / 2 : bb.height) },
+        left: { x: -size * 2 - (center ? bb.width / 2 : bb.width), y: -!center * (bb.height / 2) },
+        right: { x: size * 2 + (center ? bb.width / 2 : bb.width), y: -!center * (bb.height / 2) }
+    }[dir];
+
+    this.translate(xy.x, xy.y);
+    return paper.path(paths[dir]).attr({ fill: "#000", stroke: "none" }).insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.tag
+ [ method ]
+ **
+ * Puts the context Element in a 'tag' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - r (number) radius of the loop [default: `5`]
+ - x (number) x coordinate of the center of the tag loop [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the center of the tag loop [default: Element's `x` or `cx`]
+ **
+ = (object) path element of the tag
+ \*/
+Raphael.el.tag = function (angle, r, x, y) {
+    var d = 3,
+        paper = this.paper || this[0].paper;
+
+    if (!paper) return;
+
+    var p = paper.path().attr({ fill: '#000', stroke: '#000' }),
+        bb = this.getBBox(),
+        dx, R, center, tmp;
+
+    switch (this.type) {
+        case 'text':
+        case 'circle':
+        case 'ellipse': center = true; break;
+        default: center = false;
+    }
+
+    angle = angle || 0;
+    x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+    y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
+    r = r == null ? 5 : r;
+    R = .5522 * r;
+
+    if (bb.height >= r * 2) {
+        p.attr({
+            path: [
+                "M", x, y + r,
+                "a", r, r, 0, 1, 1, 0, -r * 2, r, r, 0, 1, 1, 0, r * 2,
+                "m", 0, -r * 2 -d,
+                "a", r + d, r + d, 0, 1, 0, 0, (r + d) * 2,
+                "L", x + r + d, y + bb.height / 2 + d,
+                "l", bb.width + 2 * d, 0, 0, -bb.height - 2 * d, -bb.width - 2 * d, 0,
+                "L", x, y - r - d
+            ].join(",")
+        });
+    } else {
+        dx = Math.sqrt(Math.pow(r + d, 2) - Math.pow(bb.height / 2 + d, 2));
+        p.attr({
+            path: [
+                "M", x, y + r,
+                "c", -R, 0, -r, R - r, -r, -r, 0, -R, r - R, -r, r, -r, R, 0, r, r - R, r, r, 0, R, R - r, r, -r, r,
+                "M", x + dx, y - bb.height / 2 - d,
+                "a", r + d, r + d, 0, 1, 0, 0, bb.height + 2 * d,
+                "l", r + d - dx + bb.width + 2 * d, 0, 0, -bb.height - 2 * d,
+                "L", x + dx, y - bb.height / 2 - d
+            ].join(",")
+        });
+    }
+
+    angle = 360 - angle;
+    p.rotate(angle, x, y);
+
+    if (this.attrs) {
+        //elements
+        this.attr(this.attrs.x ? 'x' : 'cx', x + r + d + (!center ? this.type == 'text' ? bb.width : 0 : bb.width / 2)).attr('y', center ? y : y - bb.height / 2);
+        this.rotate(angle, x, y);
+        angle > 90 && angle < 270 && this.attr(this.attrs.x ? 'x' : 'cx', x - r - d - (!center ? bb.width : bb.width / 2)).rotate(180, x, y);
+    } else {
+        //sets
+        if (angle > 90 && angle < 270) {
+            this.translate(x - bb.x - bb.width - r - d, y - bb.y - bb.height / 2);
+            this.rotate(angle - 180, bb.x + bb.width + r + d, bb.y + bb.height / 2);
+        } else {
+            this.translate(x - bb.x + r + d, y - bb.y - bb.height / 2);
+            this.rotate(angle, bb.x - r - d, bb.y + bb.height / 2); 
+        }
+    }
+
+    return p.insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.drop
+ [ method ]
+ **
+ * Puts the context Element in a 'drop' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - x (number) x coordinate of the drop's point [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the drop's point [default: Element's `x` or `cx`]
+ **
+ = (object) path element of the drop
+ \*/
+Raphael.el.drop = function (angle, x, y) {
+    var bb = this.getBBox(),
+        paper = this.paper || this[0].paper,
+        center, size, p, dx, dy;
+
+    if (!paper) return;
+
+    switch (this.type) {
+        case 'text':
+        case 'circle':
+        case 'ellipse': center = true; break;
+        default: center = false;
+    }
+
+    angle = angle || 0;
+
+    x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+    y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
+    size = Math.max(bb.width, bb.height) + Math.min(bb.width, bb.height);
+    p = paper.path([
+        "M", x, y,
+        "l", size, 0,
+        "A", size * .4, size * .4, 0, 1, 0, x + size * .7, y - size * .7,
+        "z"
+    ]).attr({fill: "#000", stroke: "none"}).rotate(22.5 - angle, x, y);
+
+    angle = (angle + 90) * Math.PI / 180;
+    dx = (x + size * Math.sin(angle)) - (center ? 0 : bb.width / 2);
+    dy = (y + size * Math.cos(angle)) - (center ? 0 : bb.height / 2);
+
+    this.attrs ?
+        this.attr(this.attrs.x ? 'x' : 'cx', dx).attr(this.attrs.y ? 'y' : 'cy', dy) :
+        this.translate(dx - bb.x, dy - bb.y);
+
+    return p.insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.flag
+ [ method ]
+ **
+ * Puts the context Element in a 'flag' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - x (number) x coordinate of the flag's point [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the flag's point [default: Element's `x` or `cx`]
+ **
+ = (object) path element of the flag
+ \*/
+Raphael.el.flag = function (angle, x, y) {
+    var d = 3,
+        paper = this.paper || this[0].paper;
+
+    if (!paper) return;
+
+    var p = paper.path().attr({ fill: '#000', stroke: '#000' }),
+        bb = this.getBBox(),
+        h = bb.height / 2,
+        center;
+
+    switch (this.type) {
+        case 'text':
+        case 'circle':
+        case 'ellipse': center = true; break;
+        default: center = false;
+    }
+
+    angle = angle || 0;
+    x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+    y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2: bb.y);
+
+    p.attr({
+        path: [
+            "M", x, y,
+            "l", h + d, -h - d, bb.width + 2 * d, 0, 0, bb.height + 2 * d, -bb.width - 2 * d, 0,
+            "z"
+        ].join(",")
+    });
+
+    angle = 360 - angle;
+    p.rotate(angle, x, y);
+
+    if (this.attrs) {
+        //elements
+        this.attr(this.attrs.x ? 'x' : 'cx', x + h + d + (!center ? this.type == 'text' ? bb.width : 0 : bb.width / 2)).attr('y', center ? y : y - bb.height / 2);
+        this.rotate(angle, x, y);
+        angle > 90 && angle < 270 && this.attr(this.attrs.x ? 'x' : 'cx', x - h - d - (!center ? bb.width : bb.width / 2)).rotate(180, x, y);
+    } else {
+        //sets
+        if (angle > 90 && angle < 270) {
+            this.translate(x - bb.x - bb.width - h - d, y - bb.y - bb.height / 2);
+            this.rotate(angle - 180, bb.x + bb.width + h + d, bb.y + bb.height / 2);
+        } else {
+            this.translate(x - bb.x + h + d, y - bb.y - bb.height / 2);
+            this.rotate(angle, bb.x - h - d, bb.y + bb.height / 2);
+        }
+    }
+
+    return p.insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.label
+ [ method ]
+ **
+ * Puts the context Element in a 'label' tooltip. Can also be used on sets.
+ **
+ = (object) path element of the label.
+ \*/
+Raphael.el.label = function () {
+    var bb = this.getBBox(),
+        paper = this.paper || this[0].paper,
+        r = Math.min(20, bb.width + 10, bb.height + 10) / 2;
+
+    if (!paper) return;
+
+    return paper.rect(bb.x - r / 2, bb.y - r / 2, bb.width + r, bb.height + r, r).attr({ stroke: 'none', fill: '#000' }).insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.blob
+ [ method ]
+ **
+ * Puts the context Element in a 'blob' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - x (number) x coordinate of the blob's tail [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the blob's tail [default: Element's `x` or `cx`]
+ **
+ = (object) path element of the blob
+ \*/
+Raphael.el.blob = function (angle, x, y) {
+    var bb = this.getBBox(),
+        rad = Math.PI / 180,
+        paper = this.paper || this[0].paper,
+        p, center, size;
+
+    if (!paper) return;
+
+    switch (this.type) {
+        case 'text':
+        case 'circle':
+        case 'ellipse': center = true; break;
+        default: center = false;
+    }
+
+    p = paper.path().attr({ fill: "#000", stroke: "none" });
+    angle = (+angle + 1 ? angle : 45) + 90;
+    size = Math.min(bb.height, bb.width);
+    x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+    y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
+
+    var w = Math.max(bb.width + size, size * 25 / 12),
+        h = Math.max(bb.height + size, size * 25 / 12),
+        x2 = x + size * Math.sin((angle - 22.5) * rad),
+        y2 = y + size * Math.cos((angle - 22.5) * rad),
+        x1 = x + size * Math.sin((angle + 22.5) * rad),
+        y1 = y + size * Math.cos((angle + 22.5) * rad),
+        dx = (x1 - x2) / 2,
+        dy = (y1 - y2) / 2,
+        rx = w / 2,
+        ry = h / 2,
+        k = -Math.sqrt(Math.abs(rx * rx * ry * ry - rx * rx * dy * dy - ry * ry * dx * dx) / (rx * rx * dy * dy + ry * ry * dx * dx)),
+        cx = k * rx * dy / ry + (x1 + x2) / 2,
+        cy = k * -ry * dx / rx + (y1 + y2) / 2;
+
+    p.attr({
+        x: cx,
+        y: cy,
+        path: [
+            "M", x, y,
+            "L", x1, y1,
+            "A", rx, ry, 0, 1, 1, x2, y2,
+            "z"
+        ].join(",")
+    });
+
+    this.translate(cx - bb.x - bb.width / 2, cy - bb.y - bb.height / 2);
+
+    return p.insertBefore(this.node ? this : this[0]);
+};
+
+/*
+ * Tooltips on Paper prototype
+ */
+/*\
+ * Paper.label
+ [ method ]
+ **
+ * Puts the given `text` into a 'label' tooltip. The text is given a default style according to @g.txtattr. See @Element.label
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the center of the label
+ - y (number) y coordinate of the center of the label
+ - text (string) text to place inside the label
+ **
+ = (object) set containing the label path and the text element
+ > Usage
+ | paper.label(50, 50, "$9.99");
+ \*/
+Raphael.fn.label = function (x, y, text) {
+    var set = this.set();
+
+    text = this.text(x, y, text).attr(Raphael.g.txtattr);
+    return set.push(text.label(), text);
+};
+
+/*\
+ * Paper.popup
+ [ method ]
+ **
+ * Puts the given `text` into a 'popup' tooltip. The text is given a default style according to @g.txtattr. See @Element.popup
+ *
+ * Note: The `dir` parameter has changed from g.Raphael 0.4.1 to 0.5. The options `0`, `1`, `2`, and `3` has been changed to `'down'`, `'left'`, `'up'`, and `'right'` respectively.
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the popup's tail
+ - y (number) y coordinate of the popup's tail
+ - text (string) text to place inside the popup
+ - dir (string) location of the text relative to the tail: `'down'`, `'left'`, `'up'` [default], or `'right'`.
+ - size (number) amount of padding around the Element [default: `5`]
+ **
+ = (object) set containing the popup path and the text element
+ > Usage
+ | paper.popup(50, 50, "$9.99", 'down');
+ \*/
+Raphael.fn.popup = function (x, y, text, dir, size) {
+    var set = this.set();
+
+    text = this.text(x, y, text).attr(Raphael.g.txtattr);
+    return set.push(text.popup(dir, size), text);
+};
+
+/*\
+ * Paper.tag
+ [ method ]
+ **
+ * Puts the given text into a 'tag' tooltip. The text is given a default style according to @g.txtattr. See @Element.tag
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the center of the tag loop
+ - y (number) y coordinate of the center of the tag loop
+ - text (string) text to place inside the tag
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - r (number) radius of the loop [default: `5`]
+ **
+ = (object) set containing the tag path and the text element
+ > Usage
+ | paper.tag(50, 50, "$9.99", 60);
+ \*/
+Raphael.fn.tag = function (x, y, text, angle, r) {
+    var set = this.set();
+
+    text = this.text(x, y, text).attr(Raphael.g.txtattr);
+    return set.push(text.tag(angle, r), text);
+};
+
+/*\
+ * Paper.flag
+ [ method ]
+ **
+ * Puts the given `text` into a 'flag' tooltip. The text is given a default style according to @g.txtattr. See @Element.flag
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the flag's point
+ - y (number) y coordinate of the flag's point
+ - text (string) text to place inside the flag
+ - angle (number) angle of orientation in degrees [default: `0`]
+ **
+ = (object) set containing the flag path and the text element
+ > Usage
+ | paper.flag(50, 50, "$9.99", 60);
+ \*/
+Raphael.fn.flag = function (x, y, text, angle) {
+    var set = this.set();
+
+    text = this.text(x, y, text).attr(Raphael.g.txtattr);
+    return set.push(text.flag(angle), text);
+};
+
+/*\
+ * Paper.drop
+ [ method ]
+ **
+ * Puts the given text into a 'drop' tooltip. The text is given a default style according to @g.txtattr. See @Element.drop
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the drop's point
+ - y (number) y coordinate of the drop's point
+ - text (string) text to place inside the drop
+ - angle (number) angle of orientation in degrees [default: `0`]
+ **
+ = (object) set containing the drop path and the text element
+ > Usage
+ | paper.drop(50, 50, "$9.99", 60);
+ \*/
+Raphael.fn.drop = function (x, y, text, angle) {
+    var set = this.set();
+
+    text = this.text(x, y, text).attr(Raphael.g.txtattr);
+    return set.push(text.drop(angle), text);
+};
+
+/*\
+ * Paper.blob
+ [ method ]
+ **
+ * Puts the given text into a 'blob' tooltip. The text is given a default style according to @g.txtattr. See @Element.blob
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the blob's tail
+ - y (number) y coordinate of the blob's tail
+ - text (string) text to place inside the blob
+ - angle (number) angle of orientation in degrees [default: `0`]
+ **
+ = (object) set containing the blob path and the text element
+ > Usage
+ | paper.blob(50, 50, "$9.99", 60);
+ \*/
+Raphael.fn.blob = function (x, y, text, angle) {
+    var set = this.set();
+
+    text = this.text(x, y, text).attr(Raphael.g.txtattr);
+    return set.push(text.blob(angle), text);
+};
+
+/**
+ * Brightness functions on the Element prototype
+ */
+/*\
+ * Element.lighter
+ [ method ]
+ **
+ * Makes the context element lighter by increasing the brightness and reducing the saturation by a given factor. Can be called on Sets.
+ **
+ > Parameters
+ **
+ - times (number) adjustment factor [default: `2`]
+ **
+ = (object) Element
+ > Usage
+ | paper.circle(50, 50, 20).attr({
+ |     fill: "#ff0000",
+ |     stroke: "#fff",
+ |     "stroke-width": 2
+ | }).lighter(6);
+ \*/
+Raphael.el.lighter = function (times) {
+    times = times || 2;
+
+    var fs = [this.attrs.fill, this.attrs.stroke];
+
+    this.fs = this.fs || [fs[0], fs[1]];
+
+    fs[0] = Raphael.rgb2hsb(Raphael.getRGB(fs[0]).hex);
+    fs[1] = Raphael.rgb2hsb(Raphael.getRGB(fs[1]).hex);
+    fs[0].b = Math.min(fs[0].b * times, 1);
+    fs[0].s = fs[0].s / times;
+    fs[1].b = Math.min(fs[1].b * times, 1);
+    fs[1].s = fs[1].s / times;
+
+    this.attr({fill: "hsb(" + [fs[0].h, fs[0].s, fs[0].b] + ")", stroke: "hsb(" + [fs[1].h, fs[1].s, fs[1].b] + ")"});
+    return this;
+};
+
+/*\
+ * Element.darker
+ [ method ]
+ **
+ * Makes the context element darker by decreasing the brightness and increasing the saturation by a given factor. Can be called on Sets.
+ **
+ > Parameters
+ **
+ - times (number) adjustment factor [default: `2`]
+ **
+ = (object) Element
+ > Usage
+ | paper.circle(50, 50, 20).attr({
+ |     fill: "#ff0000",
+ |     stroke: "#fff",
+ |     "stroke-width": 2
+ | }).darker(6);
+ \*/
+Raphael.el.darker = function (times) {
+    times = times || 2;
+
+    var fs = [this.attrs.fill, this.attrs.stroke];
+
+    this.fs = this.fs || [fs[0], fs[1]];
+
+    fs[0] = Raphael.rgb2hsb(Raphael.getRGB(fs[0]).hex);
+    fs[1] = Raphael.rgb2hsb(Raphael.getRGB(fs[1]).hex);
+    fs[0].s = Math.min(fs[0].s * times, 1);
+    fs[0].b = fs[0].b / times;
+    fs[1].s = Math.min(fs[1].s * times, 1);
+    fs[1].b = fs[1].b / times;
+
+    this.attr({fill: "hsb(" + [fs[0].h, fs[0].s, fs[0].b] + ")", stroke: "hsb(" + [fs[1].h, fs[1].s, fs[1].b] + ")"});
+    return this;
+};
+
+/*\
+ * Element.resetBrightness
+ [ method ]
+ **
+ * Resets brightness and saturation levels to their original values. See @Element.lighter and @Element.darker. Can be called on Sets.
+ **
+ = (object) Element
+ > Usage
+ | paper.circle(50, 50, 20).attr({
+ |     fill: "#ff0000",
+ |     stroke: "#fff",
+ |     "stroke-width": 2
+ | }).lighter(6).resetBrightness();
+ \*/
+Raphael.el.resetBrightness = function () {
+    if (this.fs) {
+        this.attr({ fill: this.fs[0], stroke: this.fs[1] });
+        delete this.fs;
+    }
+    return this;
+};
+
+//alias to set prototype
+(function () {
+    var brightness = ['lighter', 'darker', 'resetBrightness'],
+        tooltips = ['popup', 'tag', 'flag', 'label', 'drop', 'blob'];
+
+    for (var f in tooltips) (function (name) {
+        Raphael.st[name] = function () {
+            return Raphael.el[name].apply(this, arguments);
+        };
+    })(tooltips[f]);
+
+    for (var f in brightness) (function (name) {
+        Raphael.st[name] = function () {
+            for (var i = 0; i < this.length; i++) {
+                this[i][name].apply(this[i], arguments);
+            }
+
+            return this;
+        };
+    })(brightness[f]);
+})();
+
+//chart prototype for storing common functions
+Raphael.g = {
+    /*\
+     * g.shim
+     [ object ]
+     **
+     * An attribute object that charts will set on all generated shims (shims being the invisible objects that mouse events are bound to)
+     **
+     > Default value
+     | { stroke: 'none', fill: '#000', 'fill-opacity': 0 }
+     \*/
+    shim: { stroke: 'none', fill: '#000', 'fill-opacity': 0 },
+
+    /*\
+     * g.txtattr
+     [ object ]
+     **
+     * An attribute object that charts and tooltips will set on any generated text
+     **
+     > Default value
+     | { font: '12px Arial, sans-serif', fill: '#fff' }
+     \*/  
+    txtattr: { font: '12px Arial, sans-serif', fill: '#fff' },
+
+    /*\
+     * g.colors
+     [ array ]
+     **
+     * An array of color values that charts will iterate through when drawing chart data values.
+     **
+     \*/
+    colors: (function () {
+            var hues = [.6, .2, .05, .1333, .75, 0],
+                colors = [];
+
+            for (var i = 0; i < 10; i++) {
+                if (i < hues.length) {
+                    colors.push('hsb(' + hues[i] + ',.75, .75)');
+                } else {
+                    colors.push('hsb(' + hues[i - hues.length] + ', 1, .5)');
+                }
+            }
+
+            return colors;
+    })(),
+    
+    snapEnds: function(from, to, steps) {
+        var f = from,
+            t = to;
+
+        if (f == t) {
+            return {from: f, to: t, power: 0};
+        }
+
+        function round(a) {
+            return Math.abs(a - .5) < .25 ? ~~(a) + .5 : Math.round(a);
+        }
+
+        var d = (t - f) / steps,
+            r = ~~(d),
+            R = r,
+            i = 0;
+
+        if (r) {
+            while (R) {
+                i--;
+                R = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
+            }
+
+            i ++;
+        } else {
+            if(d == 0 || !isFinite(d)) {
+                i = 1;
+            } else {
+                while (!r) {
+                    i = i || 1;
+                    r = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
+                    i++;
+                }
+            }
+
+            i && i--;
+        }
+
+        t = round(to * Math.pow(10, i)) / Math.pow(10, i);
+
+        if (t < to) {
+            t = round((to + .5) * Math.pow(10, i)) / Math.pow(10, i);
+        }
+
+        f = round((from - (i > 0 ? 0 : .5)) * Math.pow(10, i)) / Math.pow(10, i);
+        return { from: f, to: t, power: i };
+    },
+
+    axis: function (x, y, length, from, to, steps, orientation, labels, type, dashsize, paper) {
+        dashsize = dashsize == null ? 2 : dashsize;
+        type = type || "t";
+        steps = steps || 10;
+        paper = arguments[arguments.length-1] //paper is always last argument
+
+        var path = type == "|" || type == " " ? ["M", x + .5, y, "l", 0, .001] : orientation == 1 || orientation == 3 ? ["M", x + .5, y, "l", 0, -length] : ["M", x, y + .5, "l", length, 0],
+            ends = this.snapEnds(from, to, steps),
+            f = ends.from,
+            t = ends.to,
+            i = ends.power,
+            j = 0,
+            txtattr = { font: "11px 'Fontin Sans', Fontin-Sans, sans-serif" },
+            text = paper.set(),
+            d;
+
+        d = (t - f) / steps;
+
+        var label = f,
+            rnd = i > 0 ? i : 0;
+            dx = length / steps;
+
+        if (+orientation == 1 || +orientation == 3) {
+            var Y = y,
+                addon = (orientation - 1 ? 1 : -1) * (dashsize + 3 + !!(orientation - 1));
+
+            while (Y >= y - length) {
+                type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), Y + .5, "l", dashsize * 2 + 1, 0]));
+                text.push(paper.text(x + addon, Y, (labels && labels[j++]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr).attr({ "text-anchor": orientation - 1 ? "start" : "end" }));
+                label += d;
+                Y -= dx;
+            }
+
+            if (Math.round(Y + dx - (y - length))) {
+                type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), y - length + .5, "l", dashsize * 2 + 1, 0]));
+                text.push(paper.text(x + addon, y - length, (labels && labels[j]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr).attr({ "text-anchor": orientation - 1 ? "start" : "end" }));
+            }
+        } else {
+            label = f;
+            rnd = (i > 0) * i;
+            addon = (orientation ? -1 : 1) * (dashsize + 9 + !orientation);
+
+            var X = x,
+                dx = length / steps,
+                txt = 0,
+                prev = 0;
+
+            while (X <= x + length) {
+                type != "-" && type != " " && (path = path.concat(["M", X + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
+                text.push(txt = paper.text(X, y + addon, (labels && labels[j++]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));
+
+                var bb = txt.getBBox();
+
+                if (prev >= bb.x - 5) {
+                    text.pop(text.length - 1).remove();
+                } else {
+                    prev = bb.x + bb.width;
+                }
+
+                label += d;
+                X += dx;
+            }
+
+            if (Math.round(X - dx - x - length)) {
+                type != "-" && type != " " && (path = path.concat(["M", x + length + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
+                text.push(paper.text(x + length, y + addon, (labels && labels[j]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));
+            }
+        }
+
+        var res = paper.path(path);
+
+        res.text = text;
+        res.all = paper.set([res, text]);
+        res.remove = function () {
+            this.text.remove();
+            this.constructor.prototype.remove.call(this);
+        };
+
+        return res;
+    },
+    
+    labelise: function(label, val, total) {
+        if (label) {
+            return (label + "").replace(/(##+(?:\.#+)?)|(%%+(?:\.%+)?)/g, function (all, value, percent) {
+                if (value) {
+                    return (+val).toFixed(value.replace(/^#+\.?/g, "").length);
+                }
+                if (percent) {
+                    return (val * 100 / total).toFixed(percent.replace(/^%+\.?/g, "").length) + "%";
+                }
+            });
+        } else {
+            return (+val).toFixed(0);
+        }
+    }
+}
diff --git a/vendor/assets/javascripts/jquery.nicescroll.js b/vendor/assets/javascripts/jquery.nicescroll.js
new file mode 100644
index 0000000000000000000000000000000000000000..7653f25df4b573611975293b1f8fa4fa616ab954
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.nicescroll.js
@@ -0,0 +1,3634 @@
+/* jquery.nicescroll
+-- version 3.6.0
+-- copyright 2014-11-21 InuYaksa*2014
+-- licensed under the MIT
+--
+-- http://nicescroll.areaaperta.com/
+-- https://github.com/inuyaksa/jquery.nicescroll
+--
+*/
+
+(function(factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as anonymous module.
+    define(['jquery'], factory);
+  } else {
+    // Browser globals.
+    factory(jQuery);
+  }
+}(function(jQuery) {
+  "use strict";
+
+  // globals
+  var domfocus = false;
+  var mousefocus = false;
+  var tabindexcounter = 0;
+  var ascrailcounter = 2000;
+  var globalmaxzindex = 0;
+
+  var $ = jQuery; // sandbox
+
+  // http://stackoverflow.com/questions/2161159/get-script-path
+  function getScriptPath() {
+    var scripts = document.getElementsByTagName('script');
+    var path = scripts[scripts.length - 1].src.split('?')[0];
+    return (path.split('/').length > 0) ? path.split('/').slice(0, -1).join('/') + '/' : '';
+  }
+
+  var vendors = ['webkit','ms','moz','o'];
+
+  var setAnimationFrame = window.requestAnimationFrame || false;
+  var clearAnimationFrame = window.cancelAnimationFrame || false;
+
+  if (!setAnimationFrame) {  // legacy detection
+    for (var vx in vendors) {
+      var v = vendors[vx];
+      if (!setAnimationFrame) setAnimationFrame = window[v + 'RequestAnimationFrame'];
+      if (!clearAnimationFrame) clearAnimationFrame = window[v + 'CancelAnimationFrame'] || window[v + 'CancelRequestAnimationFrame'];
+    }
+  }
+
+  var ClsMutationObserver = window.MutationObserver || window.WebKitMutationObserver || false;
+
+  var _globaloptions = {
+    zindex: "auto",
+    cursoropacitymin: 0,
+    cursoropacitymax: 1,
+    cursorcolor: "#424242",
+    cursorwidth: "5px",
+    cursorborder: "1px solid #fff",
+    cursorborderradius: "5px",
+    scrollspeed: 60,
+    mousescrollstep: 8 * 3,
+    touchbehavior: false,
+    hwacceleration: true,
+    usetransition: true,
+    boxzoom: false,
+    dblclickzoom: true,
+    gesturezoom: true,
+    grabcursorenabled: true,
+    autohidemode: true,
+    background: "",
+    iframeautoresize: true,
+    cursorminheight: 32,
+    preservenativescrolling: true,
+    railoffset: false,
+    railhoffset: false,
+    bouncescroll: true,
+    spacebarenabled: true,
+    railpadding: {
+      top: 0,
+      right: 0,
+      left: 0,
+      bottom: 0
+    },
+    disableoutline: true,
+    horizrailenabled: true,
+    railalign: "right",
+    railvalign: "bottom",
+    enabletranslate3d: true,
+    enablemousewheel: true,
+    enablekeyboard: true,
+    smoothscroll: true,
+    sensitiverail: true,
+    enablemouselockapi: true,
+    //      cursormaxheight:false,
+    cursorfixedheight: false,
+    directionlockdeadzone: 6,
+    hidecursordelay: 400,
+    nativeparentscrolling: true,
+    enablescrollonselection: true,
+    overflowx: true,
+    overflowy: true,
+    cursordragspeed: 0.3,
+    rtlmode: "auto",
+    cursordragontouch: false,
+    oneaxismousemode: "auto",
+    scriptpath: getScriptPath(),
+    preventmultitouchscrolling: true
+  };
+
+  var browserdetected = false;
+
+  var getBrowserDetection = function() {
+
+    if (browserdetected) return browserdetected;
+
+    var _el = document.createElement('DIV'),
+        _style = _el.style,
+        _agent = navigator.userAgent,
+        _platform = navigator.platform,
+        d = {};
+
+    d.haspointerlock = "pointerLockElement" in document || "webkitPointerLockElement" in document || "mozPointerLockElement" in document;
+
+    d.isopera = ("opera" in window); // 12-
+    d.isopera12 = (d.isopera && ("getUserMedia" in navigator));
+    d.isoperamini = (Object.prototype.toString.call(window.operamini) === "[object OperaMini]");
+
+    d.isie = (("all" in document) && ("attachEvent" in _el) && !d.isopera); //IE10-
+    d.isieold = (d.isie && !("msInterpolationMode" in _style)); // IE6 and older
+    d.isie7 = d.isie && !d.isieold && (!("documentMode" in document) || (document.documentMode == 7));
+    d.isie8 = d.isie && ("documentMode" in document) && (document.documentMode == 8);
+    d.isie9 = d.isie && ("performance" in window) && (document.documentMode >= 9);
+    d.isie10 = d.isie && ("performance" in window) && (document.documentMode == 10);
+    d.isie11 = ("msRequestFullscreen" in _el) && (document.documentMode >= 11); // IE11+
+
+    d.isie9mobile = /iemobile.9/i.test(_agent); //wp 7.1 mango
+    if (d.isie9mobile) d.isie9 = false;
+    d.isie7mobile = (!d.isie9mobile && d.isie7) && /iemobile/i.test(_agent); //wp 7.0
+
+    d.ismozilla = ("MozAppearance" in _style);
+
+    d.iswebkit = ("WebkitAppearance" in _style);
+
+    d.ischrome = ("chrome" in window);
+    d.ischrome22 = (d.ischrome && d.haspointerlock);
+    d.ischrome26 = (d.ischrome && ("transition" in _style)); // issue with transform detection (maintain prefix)
+
+    d.cantouch = ("ontouchstart" in document.documentElement) || ("ontouchstart" in window); // detection for Chrome Touch Emulation
+    d.hasmstouch = (window.MSPointerEvent || false); // IE10 pointer events
+    d.hasw3ctouch = (window.PointerEvent || false); //IE11 pointer events, following W3C Pointer Events spec
+
+    d.ismac = /^mac$/i.test(_platform);
+
+    d.isios = (d.cantouch && /iphone|ipad|ipod/i.test(_platform));
+    d.isios4 = ((d.isios) && !("seal" in Object));
+    d.isios7 = ((d.isios)&&("webkitHidden" in document));  //iOS 7+
+
+    d.isandroid = (/android/i.test(_agent));
+
+    d.haseventlistener = ("addEventListener" in _el);
+    
+    d.trstyle = false;
+    d.hastransform = false;
+    d.hastranslate3d = false;
+    d.transitionstyle = false;
+    d.hastransition = false;
+    d.transitionend = false;
+
+    var a;
+    var check = ['transform', 'msTransform', 'webkitTransform', 'MozTransform', 'OTransform'];    
+    for (a = 0; a < check.length; a++) {
+      if (typeof _style[check[a]] != "undefined") {
+        d.trstyle = check[a];
+        break;
+      }
+    }
+    d.hastransform = (!!d.trstyle);
+    if (d.hastransform) {
+      _style[d.trstyle] = "translate3d(1px,2px,3px)";
+      d.hastranslate3d = /translate3d/.test(_style[d.trstyle]);
+    }
+
+    d.transitionstyle = false;
+    d.prefixstyle = '';
+    d.transitionend = false;
+    check = ['transition', 'webkitTransition', 'msTransition', 'MozTransition', 'OTransition', 'OTransition', 'KhtmlTransition'];
+    var prefix = ['', '-webkit-', '-ms-', '-moz-', '-o-', '-o', '-khtml-'];
+    var evs = ['transitionend', 'webkitTransitionEnd', 'msTransitionEnd',  'transitionend', 'otransitionend', 'oTransitionEnd', 'KhtmlTransitionEnd'];
+    for (a = 0; a < check.length; a++) {
+      if (check[a] in _style) {
+        d.transitionstyle = check[a];
+        d.prefixstyle = prefix[a];
+        d.transitionend = evs[a];
+        break;
+      }
+    }
+    if (d.ischrome26) {  // always use prefix
+      d.prefixstyle = prefix[1];
+    }
+
+    d.hastransition = (d.transitionstyle);
+
+    function detectCursorGrab() {
+      var lst = ['-webkit-grab', '-moz-grab', 'grab'];
+      if ((d.ischrome && !d.ischrome22) || d.isie) lst = []; // force setting for IE returns false positive and chrome cursor bug
+      for (var a = 0; a < lst.length; a++) {
+        var p = lst[a];
+        _style.cursor = p;
+        if (_style.cursor == p) return p;
+      }
+      return 'url(//mail.google.com/mail/images/2/openhand.cur),n-resize'; // thank you google for custom cursor!
+    }
+    d.cursorgrabvalue = detectCursorGrab();
+
+    d.hasmousecapture = ("setCapture" in _el);
+
+    d.hasMutationObserver = (ClsMutationObserver !== false);
+
+    _el = null; //memory released
+
+    browserdetected = d;
+
+    return d;
+  };
+
+  var NiceScrollClass = function(myopt, me) {
+
+    var self = this;
+
+    this.version = '3.6.0';
+    this.name = 'nicescroll';
+
+    this.me = me;
+
+    this.opt = {
+      doc: $("body"),
+      win: false
+    };
+
+    $.extend(this.opt, _globaloptions);  // clone opts
+
+    // Options for internal use
+    this.opt.snapbackspeed = 80;
+
+    if (myopt || false) {
+      for (var a in self.opt) {
+        if (typeof myopt[a] != "undefined") self.opt[a] = myopt[a];
+      }
+    }
+
+    this.doc = self.opt.doc;
+    this.iddoc = (this.doc && this.doc[0]) ? this.doc[0].id || '' : '';
+    this.ispage = /^BODY|HTML/.test((self.opt.win) ? self.opt.win[0].nodeName : this.doc[0].nodeName);
+    this.haswrapper = (self.opt.win !== false);
+    this.win = self.opt.win || (this.ispage ? $(window) : this.doc);
+    this.docscroll = (this.ispage && !this.haswrapper) ? $(window) : this.win;
+    this.body = $("body");
+    this.viewport = false;
+
+    this.isfixed = false;
+
+    this.iframe = false;
+    this.isiframe = ((this.doc[0].nodeName == 'IFRAME') && (this.win[0].nodeName == 'IFRAME'));
+
+    this.istextarea = (this.win[0].nodeName == 'TEXTAREA');
+
+    this.forcescreen = false; //force to use screen position on events
+
+    this.canshowonmouseevent = (self.opt.autohidemode != "scroll");
+
+    // Events jump table    
+    this.onmousedown = false;
+    this.onmouseup = false;
+    this.onmousemove = false;
+    this.onmousewheel = false;
+    this.onkeypress = false;
+    this.ongesturezoom = false;
+    this.onclick = false;
+
+    // Nicescroll custom events
+    this.onscrollstart = false;
+    this.onscrollend = false;
+    this.onscrollcancel = false;
+
+    this.onzoomin = false;
+    this.onzoomout = false;
+
+    // Let's start!  
+    this.view = false;
+    this.page = false;
+
+    this.scroll = {
+      x: 0,
+      y: 0
+    };
+    this.scrollratio = {
+      x: 0,
+      y: 0
+    };
+    this.cursorheight = 20;
+    this.scrollvaluemax = 0;
+
+    this.isrtlmode = (this.opt.rtlmode == "auto") ? ((this.win[0] == window ? this.body : this.win).css("direction") == "rtl") : (this.opt.rtlmode === true);
+    //    this.checkrtlmode = false;
+    
+    this.scrollrunning = false;
+
+    this.scrollmom = false;
+
+    this.observer        = false;  // observer div changes
+    this.observerremover = false;  // observer on parent for remove detection
+    this.observerbody    = false;  // observer on body for position change
+
+    do {
+      this.id = "ascrail" + (ascrailcounter++);
+    } while (document.getElementById(this.id));
+
+    this.rail = false;
+    this.cursor = false;
+    this.cursorfreezed = false;
+    this.selectiondrag = false;
+
+    this.zoom = false;
+    this.zoomactive = false;
+
+    this.hasfocus = false;
+    this.hasmousefocus = false;
+
+    this.visibility = true;
+    this.railslocked = false;  // locked by resize
+    this.locked = false;  // prevent lost of locked status sets by user
+    this.hidden = false; // rails always hidden
+    this.cursoractive = true; // user can interact with cursors
+
+    this.wheelprevented = false; //prevent mousewheel event
+
+    this.overflowx = self.opt.overflowx;
+    this.overflowy = self.opt.overflowy;
+
+    this.nativescrollingarea = false;
+    this.checkarea = 0;
+
+    this.events = []; // event list for unbind
+
+    this.saved = {};  // style saved
+
+    this.delaylist = {};
+    this.synclist = {};
+
+    this.lastdeltax = 0;
+    this.lastdeltay = 0;
+
+    this.detected = getBrowserDetection();
+
+    var cap = $.extend({}, this.detected);
+
+    this.canhwscroll = (cap.hastransform && self.opt.hwacceleration);
+    this.ishwscroll = (this.canhwscroll && self.haswrapper);
+
+    this.hasreversehr = (this.isrtlmode&&!cap.iswebkit);  //RTL mode with reverse horizontal axis
+    
+    this.istouchcapable = false; // desktop devices with touch screen support
+
+    //## Check WebKit-based desktop with touch support
+    //## + Firefox 18 nightly build (desktop) false positive (or desktop with touch support)
+    if (cap.cantouch && !cap.isios && !cap.isandroid && (cap.iswebkit || cap.ismozilla)) {
+      this.istouchcapable = true;
+      cap.cantouch = false; // parse normal desktop events
+    }
+
+    //## disable MouseLock API on user request
+    if (!self.opt.enablemouselockapi) {
+      cap.hasmousecapture = false;
+      cap.haspointerlock = false;
+    }
+
+/* deprecated
+    this.delayed = function(name, fn, tm, lazy) {
+    };
+*/    
+
+    this.debounced = function(name, fn, tm) {
+      var dd = self.delaylist[name];
+      self.delaylist[name] = fn;
+      if (!dd) {
+        setTimeout(function() {
+          var fn = self.delaylist[name];
+          self.delaylist[name] = false;
+          fn.call(self);
+        }, tm);
+      }
+    };
+
+    var _onsync = false;
+
+    this.synched = function(name, fn) {
+
+      function requestSync() {
+        if (_onsync) return;
+        setAnimationFrame(function() {
+          _onsync = false;
+          for (var nn in self.synclist) {
+            var fn = self.synclist[nn];
+            if (fn) fn.call(self);
+            self.synclist[nn] = false;
+          }
+        });
+        _onsync = true;
+      }
+
+      self.synclist[name] = fn;
+      requestSync();
+      return name;
+    };
+
+    this.unsynched = function(name) {
+      if (self.synclist[name]) self.synclist[name] = false;
+    };
+
+    this.css = function(el, pars) { // save & set
+      for (var n in pars) {
+        self.saved.css.push([el, n, el.css(n)]);
+        el.css(n, pars[n]);
+      }
+    };
+
+    this.scrollTop = function(val) {
+      return (typeof val == "undefined") ? self.getScrollTop() : self.setScrollTop(val);
+    };
+
+    this.scrollLeft = function(val) {
+      return (typeof val == "undefined") ? self.getScrollLeft() : self.setScrollLeft(val);
+    };
+
+    // derived by by Dan Pupius www.pupius.net
+    var BezierClass = function(st, ed, spd, p1, p2, p3, p4) {
+    
+      this.st = st;
+      this.ed = ed;
+      this.spd = spd;
+
+      this.p1 = p1 || 0;
+      this.p2 = p2 || 1;
+      this.p3 = p3 || 0;
+      this.p4 = p4 || 1;
+
+      this.ts = (new Date()).getTime();
+      this.df = this.ed - this.st;
+    };
+    BezierClass.prototype = {
+      B2: function(t) {
+        return 3 * t * t * (1 - t);
+      },
+      B3: function(t) {
+        return 3 * t * (1 - t) * (1 - t);
+      },
+      B4: function(t) {
+        return (1 - t) * (1 - t) * (1 - t);
+      },
+      getNow: function() {
+        var nw = (new Date()).getTime();
+        var pc = 1 - ((nw - this.ts) / this.spd);
+        var bz = this.B2(pc) + this.B3(pc) + this.B4(pc);
+        return (pc < 0) ? this.ed : this.st + Math.round(this.df * bz);
+      },
+      update: function(ed, spd) {
+        this.st = this.getNow();
+        this.ed = ed;
+        this.spd = spd;
+        this.ts = (new Date()).getTime();
+        this.df = this.ed - this.st;
+        return this;
+      }
+    };
+
+    //derived from http://stackoverflow.com/questions/11236090/
+    function getMatrixValues() {
+      var tr = self.doc.css(cap.trstyle);
+      if (tr && (tr.substr(0, 6) == "matrix")) {
+        return tr.replace(/^.*\((.*)\)$/g, "$1").replace(/px/g, '').split(/, +/);
+      }
+      return false;
+    }
+
+    if (this.ishwscroll) {
+      // hw accelerated scroll
+      this.doc.translate = {
+        x: 0,
+        y: 0,
+        tx: "0px",
+        ty: "0px"
+      };
+
+      //this one can help to enable hw accel on ios6 http://indiegamr.com/ios6-html-hardware-acceleration-changes-and-how-to-fix-them/
+      if (cap.hastranslate3d && cap.isios) this.doc.css("-webkit-backface-visibility", "hidden"); // prevent flickering http://stackoverflow.com/questions/3461441/      
+
+      this.getScrollTop = function(last) {
+        if (!last) {
+          var mtx = getMatrixValues();
+          if (mtx) return (mtx.length == 16) ? -mtx[13] : -mtx[5]; //matrix3d 16 on IE10
+          if (self.timerscroll && self.timerscroll.bz) return self.timerscroll.bz.getNow();
+        }
+        return self.doc.translate.y;
+      };
+
+      this.getScrollLeft = function(last) {
+        if (!last) {
+          var mtx = getMatrixValues();
+          if (mtx) return (mtx.length == 16) ? -mtx[12] : -mtx[4]; //matrix3d 16 on IE10
+          if (self.timerscroll && self.timerscroll.bh) return self.timerscroll.bh.getNow();
+        }
+        return self.doc.translate.x;
+      };
+
+      this.notifyScrollEvent = function(el) {
+        var e = document.createEvent("UIEvents");
+        e.initUIEvent("scroll", false, true, window, 1);
+        e.niceevent = true;
+        el.dispatchEvent(e);
+      };
+
+      var cxscrollleft = (this.isrtlmode) ? 1 : -1;
+
+      if (cap.hastranslate3d && self.opt.enabletranslate3d) {
+        this.setScrollTop = function(val, silent) {
+          self.doc.translate.y = val;
+          self.doc.translate.ty = (val * -1) + "px";
+          self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0px)");
+          if (!silent) self.notifyScrollEvent(self.win[0]);
+        };
+        this.setScrollLeft = function(val, silent) {
+          self.doc.translate.x = val;
+          self.doc.translate.tx = (val * cxscrollleft) + "px";
+          self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0px)");
+          if (!silent) self.notifyScrollEvent(self.win[0]);
+        };
+      } else {
+        this.setScrollTop = function(val, silent) {
+          self.doc.translate.y = val;
+          self.doc.translate.ty = (val * -1) + "px";
+          self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")");
+          if (!silent) self.notifyScrollEvent(self.win[0]);
+        };
+        this.setScrollLeft = function(val, silent) {
+          self.doc.translate.x = val;
+          self.doc.translate.tx = (val * cxscrollleft) + "px";
+          self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")");
+          if (!silent) self.notifyScrollEvent(self.win[0]);
+        };
+      }
+    } else {
+      // native scroll
+      this.getScrollTop = function() {
+        return self.docscroll.scrollTop();
+      };
+      this.setScrollTop = function(val) {
+        return self.docscroll.scrollTop(val);
+      };
+      this.getScrollLeft = function() {
+        if (self.detected.ismozilla && self.isrtlmode)
+          return Math.abs(self.docscroll.scrollLeft());
+        return self.docscroll.scrollLeft();
+      };
+      this.setScrollLeft = function(val) {
+        return self.docscroll.scrollLeft((self.detected.ismozilla && self.isrtlmode) ? -val : val);
+      };
+    }
+
+    this.getTarget = function(e) {
+      if (!e) return false;
+      if (e.target) return e.target;
+      if (e.srcElement) return e.srcElement;
+      return false;
+    };
+
+    this.hasParent = function(e, id) {
+      if (!e) return false;
+      var el = e.target || e.srcElement || e || false;
+      while (el && el.id != id) {
+        el = el.parentNode || false;
+      }
+      return (el !== false);
+    };
+
+    function getZIndex() {
+      var dom = self.win;
+      if ("zIndex" in dom) return dom.zIndex(); // use jQuery UI method when available
+      while (dom.length > 0) {
+        if (dom[0].nodeType == 9) return false;
+        var zi = dom.css('zIndex');
+        if (!isNaN(zi) && zi != 0) return parseInt(zi);
+        dom = dom.parent();
+      }
+      return false;
+    }
+
+    //inspired by http://forum.jquery.com/topic/width-includes-border-width-when-set-to-thin-medium-thick-in-ie
+    var _convertBorderWidth = {
+      "thin": 1,
+      "medium": 3,
+      "thick": 5
+    };
+
+    function getWidthToPixel(dom, prop, chkheight) {
+      var wd = dom.css(prop);
+      var px = parseFloat(wd);
+      if (isNaN(px)) {
+        px = _convertBorderWidth[wd] || 0;
+        var brd = (px == 3) ? ((chkheight) ? (self.win.outerHeight() - self.win.innerHeight()) : (self.win.outerWidth() - self.win.innerWidth())) : 1; //DON'T TRUST CSS
+        if (self.isie8 && px) px += 1;
+        return (brd) ? px : 0;
+      }
+      return px;
+    }
+
+    this.getDocumentScrollOffset = function() {
+      return {top:window.pageYOffset||document.documentElement.scrollTop,
+              left:window.pageXOffset||document.documentElement.scrollLeft};
+    }
+    
+    this.getOffset = function() {
+      if (self.isfixed) {
+        var ofs = self.win.offset();  // fix Chrome auto issue (when right/bottom props only)
+        var scrl = self.getDocumentScrollOffset();
+        ofs.top-=scrl.top;
+        ofs.left-=scrl.left;
+        return ofs;  
+      }
+      var ww = self.win.offset();
+      if (!self.viewport) return ww;      
+      var vp = self.viewport.offset();
+      return {
+        top: ww.top - vp.top,// + self.viewport.scrollTop(),
+        left: ww.left - vp.left // + self.viewport.scrollLeft()
+      };
+    };
+
+    this.updateScrollBar = function(len) {
+      if (self.ishwscroll) {
+        self.rail.css({  //**
+          height: self.win.innerHeight() - (self.opt.railpadding.top + self.opt.railpadding.bottom)
+        });
+        if (self.railh) self.railh.css({  //**
+          width: self.win.innerWidth() - (self.opt.railpadding.left + self.opt.railpadding.right)
+        });
+        
+      } else {
+        var wpos = self.getOffset();
+        var pos = {
+          top: wpos.top,
+          left: wpos.left  - (self.opt.railpadding.left + self.opt.railpadding.right)
+        };
+        pos.top += getWidthToPixel(self.win, 'border-top-width', true);
+        pos.left += (self.rail.align) ? self.win.outerWidth() - getWidthToPixel(self.win, 'border-right-width') - self.rail.width : getWidthToPixel(self.win, 'border-left-width');
+
+        var off = self.opt.railoffset;
+        if (off) {
+          if (off.top) pos.top += off.top;
+          if (self.rail.align && off.left) pos.left += off.left;
+        }
+        
+        if (!self.railslocked) self.rail.css({
+          top: pos.top,
+          left: pos.left,
+          height: ((len) ? len.h : self.win.innerHeight()) - (self.opt.railpadding.top + self.opt.railpadding.bottom)
+        });
+
+        if (self.zoom) {
+          self.zoom.css({
+            top: pos.top + 1,
+            left: (self.rail.align == 1) ? pos.left - 20 : pos.left + self.rail.width + 4
+          });
+        }
+
+        if (self.railh && !self.railslocked) {
+          var pos = {
+            top: wpos.top,
+            left: wpos.left
+          };
+          var off = self.opt.railhoffset;
+          if (!!off) {
+            if (!!off.top) pos.top += off.top;
+            if (!!off.left) pos.left += off.left;
+          }
+          var y = (self.railh.align) ? pos.top + getWidthToPixel(self.win, 'border-top-width', true) + self.win.innerHeight() - self.railh.height : pos.top + getWidthToPixel(self.win, 'border-top-width', true);
+          var x = pos.left + getWidthToPixel(self.win, 'border-left-width');
+          self.railh.css({
+            top: y  - (self.opt.railpadding.top + self.opt.railpadding.bottom),
+            left: x,
+            width: self.railh.width
+          });
+        }
+
+
+      }
+    };
+
+    this.doRailClick = function(e, dbl, hr) {
+      var fn, pg, cur, pos;
+
+      if (self.railslocked) return;
+      self.cancelEvent(e);
+
+      if (dbl) {
+        fn = (hr) ? self.doScrollLeft : self.doScrollTop;
+        cur = (hr) ? ((e.pageX - self.railh.offset().left - (self.cursorwidth / 2)) * self.scrollratio.x) : ((e.pageY - self.rail.offset().top - (self.cursorheight / 2)) * self.scrollratio.y);
+        fn(cur);
+      } else {
+        fn = (hr) ? self.doScrollLeftBy : self.doScrollBy;
+        cur = (hr) ? self.scroll.x : self.scroll.y;
+        pos = (hr) ? e.pageX - self.railh.offset().left : e.pageY - self.rail.offset().top;
+        pg = (hr) ? self.view.w : self.view.h;
+        fn((cur >= pos) ? pg: -pg);//   (cur >= pos) ? fn(pg): fn(-pg);
+      }
+
+    };
+
+    self.hasanimationframe = (setAnimationFrame);
+    self.hascancelanimationframe = (clearAnimationFrame);
+
+    if (!self.hasanimationframe) {
+      setAnimationFrame = function(fn) {
+        return setTimeout(fn, 15 - Math.floor((+new Date()) / 1000) % 16);
+      }; // 1000/60)};
+      clearAnimationFrame = clearInterval;
+    } else if (!self.hascancelanimationframe) clearAnimationFrame = function() {
+      self.cancelAnimationFrame = true;
+    };
+
+    this.init = function() {
+
+      self.saved.css = [];
+
+      if (cap.isie7mobile) return true; // SORRY, DO NOT WORK!
+      if (cap.isoperamini) return true; // SORRY, DO NOT WORK!
+
+      if (cap.hasmstouch) self.css((self.ispage) ? $("html") : self.win, {
+        '-ms-touch-action': 'none'
+      });
+
+      self.zindex = "auto";
+      if (!self.ispage && self.opt.zindex == "auto") {
+        self.zindex = getZIndex() || "auto";
+      } else {
+        self.zindex = self.opt.zindex;
+      }
+
+      if (!self.ispage && self.zindex != "auto") {
+        if (self.zindex > globalmaxzindex) globalmaxzindex = self.zindex;
+      }
+
+      if (self.isie && self.zindex == 0 && self.opt.zindex == "auto") { // fix IE auto == 0
+        self.zindex = "auto";
+      }
+
+      if (!self.ispage || (!cap.cantouch && !cap.isieold && !cap.isie9mobile)) {
+
+        var cont = self.docscroll;
+        if (self.ispage) cont = (self.haswrapper) ? self.win : self.doc;
+
+        if (!cap.isie9mobile) self.css(cont, {
+          'overflow-y': 'hidden'
+        });
+
+        if (self.ispage && cap.isie7) {
+          if (self.doc[0].nodeName == 'BODY') self.css($("html"), {
+            'overflow-y': 'hidden'
+          }); //IE7 double scrollbar issue
+          else if (self.doc[0].nodeName == 'HTML') self.css($("body"), {
+            'overflow-y': 'hidden'
+          }); //IE7 double scrollbar issue
+        }
+
+        if (cap.isios && !self.ispage && !self.haswrapper) self.css($("body"), {
+          "-webkit-overflow-scrolling": "touch"
+        }); //force hw acceleration
+
+        var cursor = $(document.createElement('div'));
+        cursor.css({
+          position: "relative",
+          top: 0,
+          "float": "right",
+          width: self.opt.cursorwidth,
+          height: "0px",
+          'background-color': self.opt.cursorcolor,
+          border: self.opt.cursorborder,
+          'background-clip': 'padding-box',
+          '-webkit-border-radius': self.opt.cursorborderradius,
+          '-moz-border-radius': self.opt.cursorborderradius,
+          'border-radius': self.opt.cursorborderradius
+        });
+
+        cursor.hborder = parseFloat(cursor.outerHeight() - cursor.innerHeight());
+        
+        cursor.addClass('nicescroll-cursors');
+        
+        self.cursor = cursor;
+
+        var rail = $(document.createElement('div'));
+        rail.attr('id', self.id);
+        rail.addClass('nicescroll-rails nicescroll-rails-vr');
+
+        var v, a, kp = ["left","right","top","bottom"];  //**
+        for (var n in kp) {
+          a = kp[n];
+          v = self.opt.railpadding[a];
+          (v) ? rail.css("padding-"+a,v+"px") : self.opt.railpadding[a] = 0;
+        }
+
+        rail.append(cursor);
+
+        rail.width = Math.max(parseFloat(self.opt.cursorwidth), cursor.outerWidth());
+        rail.css({
+          width: rail.width + "px",
+          'zIndex': self.zindex,
+          "background": self.opt.background,
+          cursor: "default"
+        });
+
+        rail.visibility = true;
+        rail.scrollable = true;
+
+        rail.align = (self.opt.railalign == "left") ? 0 : 1;
+
+        self.rail = rail;
+
+        self.rail.drag = false;
+
+        var zoom = false;
+        if (self.opt.boxzoom && !self.ispage && !cap.isieold) {
+          zoom = document.createElement('div');
+
+          self.bind(zoom, "click", self.doZoom);
+          self.bind(zoom, "mouseenter", function() {
+            self.zoom.css('opacity', self.opt.cursoropacitymax);
+          });
+          self.bind(zoom, "mouseleave", function() {
+            self.zoom.css('opacity', self.opt.cursoropacitymin);
+          });
+
+          self.zoom = $(zoom);
+          self.zoom.css({
+            "cursor": "pointer",
+            'z-index': self.zindex,
+            'backgroundImage': 'url(' + self.opt.scriptpath + 'zoomico.png)',
+            'height': 18,
+            'width': 18,
+            'backgroundPosition': '0px 0px'
+          });
+          if (self.opt.dblclickzoom) self.bind(self.win, "dblclick", self.doZoom);
+          if (cap.cantouch && self.opt.gesturezoom) {
+            self.ongesturezoom = function(e) {
+              if (e.scale > 1.5) self.doZoomIn(e);
+              if (e.scale < 0.8) self.doZoomOut(e);
+              return self.cancelEvent(e);
+            };
+            self.bind(self.win, "gestureend", self.ongesturezoom);
+          }
+        }
+
+        // init HORIZ
+
+        self.railh = false;
+        var railh;
+
+        if (self.opt.horizrailenabled) {
+
+          self.css(cont, {
+            'overflow-x': 'hidden'
+          });
+
+          var cursor = $(document.createElement('div'));
+          cursor.css({
+            position: "absolute",
+            top: 0,
+            height: self.opt.cursorwidth,
+            width: "0px",
+            'background-color': self.opt.cursorcolor,
+            border: self.opt.cursorborder,
+            'background-clip': 'padding-box',
+            '-webkit-border-radius': self.opt.cursorborderradius,
+            '-moz-border-radius': self.opt.cursorborderradius,
+            'border-radius': self.opt.cursorborderradius
+          });
+
+          if (cap.isieold) cursor.css({'overflow':'hidden'});  //IE6 horiz scrollbar issue
+          
+          cursor.wborder = parseFloat(cursor.outerWidth() - cursor.innerWidth());
+          
+          cursor.addClass('nicescroll-cursors');
+          
+          self.cursorh = cursor;
+
+          railh = $(document.createElement('div'));
+          railh.attr('id', self.id + '-hr');
+          railh.addClass('nicescroll-rails nicescroll-rails-hr');
+          railh.height = Math.max(parseFloat(self.opt.cursorwidth), cursor.outerHeight());
+          railh.css({
+            height: railh.height + "px",
+            'zIndex': self.zindex,
+            "background": self.opt.background
+          });
+
+          railh.append(cursor);
+
+          railh.visibility = true;
+          railh.scrollable = true;
+
+          railh.align = (self.opt.railvalign == "top") ? 0 : 1;
+
+          self.railh = railh;
+
+          self.railh.drag = false;
+
+        }
+
+        //        
+
+        if (self.ispage) {
+          rail.css({
+            position: "fixed",
+            top: "0px",
+            height: "100%"
+          });
+          (rail.align) ? rail.css({
+            right: "0px"
+          }): rail.css({
+            left: "0px"
+          });
+          self.body.append(rail);
+          if (self.railh) {
+            railh.css({
+              position: "fixed",
+              left: "0px",
+              width: "100%"
+            });
+            (railh.align) ? railh.css({
+              bottom: "0px"
+            }): railh.css({
+              top: "0px"
+            });
+            self.body.append(railh);
+          }
+        } else {
+          if (self.ishwscroll) {
+            if (self.win.css('position') == 'static') self.css(self.win, {
+              'position': 'relative'
+            });
+            var bd = (self.win[0].nodeName == 'HTML') ? self.body : self.win;
+            $(bd).scrollTop(0).scrollLeft(0);  // fix rail position if content already scrolled
+            if (self.zoom) {
+              self.zoom.css({
+                position: "absolute",
+                top: 1,
+                right: 0,
+                "margin-right": rail.width + 4
+              });
+              bd.append(self.zoom);
+            }
+            rail.css({
+              position: "absolute",
+              top: 0
+            });
+            (rail.align) ? rail.css({
+              right: 0
+            }): rail.css({
+              left: 0
+            });
+            bd.append(rail);
+            if (railh) {
+              railh.css({
+                position: "absolute",
+                left: 0,
+                bottom: 0
+              });
+              (railh.align) ? railh.css({
+                bottom: 0
+              }): railh.css({
+                top: 0
+              });
+              bd.append(railh);
+            }
+          } else {
+            self.isfixed = (self.win.css("position") == "fixed");
+            var rlpos = (self.isfixed) ? "fixed" : "absolute";
+
+            if (!self.isfixed) self.viewport = self.getViewport(self.win[0]);
+            if (self.viewport) {
+              self.body = self.viewport;
+              if ((/fixed|absolute/.test(self.viewport.css("position"))) == false) self.css(self.viewport, {
+                "position": "relative"
+              });
+            }
+
+            rail.css({
+              position: rlpos
+            });
+            if (self.zoom) self.zoom.css({
+              position: rlpos
+            });
+            self.updateScrollBar();
+            self.body.append(rail);
+            if (self.zoom) self.body.append(self.zoom);
+            if (self.railh) {
+              railh.css({
+                position: rlpos
+              });
+              self.body.append(railh);
+            }
+          }
+
+          if (cap.isios) self.css(self.win, {
+            '-webkit-tap-highlight-color': 'rgba(0,0,0,0)',
+            '-webkit-touch-callout': 'none'
+          }); // prevent grey layer on click
+
+          if (cap.isie && self.opt.disableoutline) self.win.attr("hideFocus", "true"); // IE, prevent dotted rectangle on focused div
+          if (cap.iswebkit && self.opt.disableoutline) self.win.css({"outline": "none"});  // Webkit outline
+          //if (cap.isopera&&self.opt.disableoutline) self.win.css({"outline":"0"});  // Opera 12- to test [TODO]
+
+        }
+
+        if (self.opt.autohidemode === false) {
+          self.autohidedom = false;
+          self.rail.css({
+            opacity: self.opt.cursoropacitymax
+          });
+          if (self.railh) self.railh.css({
+            opacity: self.opt.cursoropacitymax
+          });
+        } else if ((self.opt.autohidemode === true) || (self.opt.autohidemode === "leave")) {
+          self.autohidedom = $().add(self.rail);
+          if (cap.isie8) self.autohidedom = self.autohidedom.add(self.cursor);
+          if (self.railh) self.autohidedom = self.autohidedom.add(self.railh);
+          if (self.railh && cap.isie8) self.autohidedom = self.autohidedom.add(self.cursorh);
+        } else if (self.opt.autohidemode == "scroll") {
+          self.autohidedom = $().add(self.rail);
+          if (self.railh) self.autohidedom = self.autohidedom.add(self.railh);
+        } else if (self.opt.autohidemode == "cursor") {
+          self.autohidedom = $().add(self.cursor);
+          if (self.railh) self.autohidedom = self.autohidedom.add(self.cursorh);
+        } else if (self.opt.autohidemode == "hidden") {
+          self.autohidedom = false;
+          self.hide();
+          self.railslocked = false;
+        }
+
+        if (cap.isie9mobile) {
+
+          self.scrollmom = new ScrollMomentumClass2D(self);
+
+          self.onmangotouch = function() {
+            var py = self.getScrollTop();
+            var px = self.getScrollLeft();
+
+            if ((py == self.scrollmom.lastscrolly) && (px == self.scrollmom.lastscrollx)) return true;
+
+            var dfy = py - self.mangotouch.sy;
+            var dfx = px - self.mangotouch.sx;
+            var df = Math.round(Math.sqrt(Math.pow(dfx, 2) + Math.pow(dfy, 2)));
+            if (df == 0) return;
+
+            var dry = (dfy < 0) ? -1 : 1;
+            var drx = (dfx < 0) ? -1 : 1;
+
+            var tm = +new Date();
+            if (self.mangotouch.lazy) clearTimeout(self.mangotouch.lazy);
+
+            if (((tm - self.mangotouch.tm) > 80) || (self.mangotouch.dry != dry) || (self.mangotouch.drx != drx)) {
+              self.scrollmom.stop();
+              self.scrollmom.reset(px, py);
+              self.mangotouch.sy = py;
+              self.mangotouch.ly = py;
+              self.mangotouch.sx = px;
+              self.mangotouch.lx = px;
+              self.mangotouch.dry = dry;
+              self.mangotouch.drx = drx;
+              self.mangotouch.tm = tm;
+            } else {
+
+              self.scrollmom.stop();
+              self.scrollmom.update(self.mangotouch.sx - dfx, self.mangotouch.sy - dfy);
+              self.mangotouch.tm = tm;
+
+              var ds = Math.max(Math.abs(self.mangotouch.ly - py), Math.abs(self.mangotouch.lx - px));
+              self.mangotouch.ly = py;
+              self.mangotouch.lx = px;
+
+              if (ds > 2) {
+                self.mangotouch.lazy = setTimeout(function() {
+                  self.mangotouch.lazy = false;
+                  self.mangotouch.dry = 0;
+                  self.mangotouch.drx = 0;
+                  self.mangotouch.tm = 0;
+                  self.scrollmom.doMomentum(30);
+                }, 100);
+              }
+            }
+          };
+
+          var top = self.getScrollTop();
+          var lef = self.getScrollLeft();
+          self.mangotouch = {
+            sy: top,
+            ly: top,
+            dry: 0,
+            sx: lef,
+            lx: lef,
+            drx: 0,
+            lazy: false,
+            tm: 0
+          };
+
+          self.bind(self.docscroll, "scroll", self.onmangotouch);
+
+        } else {
+
+          if (cap.cantouch || self.istouchcapable || self.opt.touchbehavior || cap.hasmstouch) {
+
+            self.scrollmom = new ScrollMomentumClass2D(self);
+
+            self.ontouchstart = function(e) {
+              if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false;
+              
+              self.hasmoving = false;
+
+              if (!self.railslocked) {
+
+                var tg;
+                if (cap.hasmstouch) {
+                  tg = (e.target) ? e.target : false;
+                  while (tg) {
+                    var nc = $(tg).getNiceScroll();
+                    if ((nc.length > 0) && (nc[0].me == self.me)) break;
+                    if (nc.length > 0) return false;
+                    if ((tg.nodeName == 'DIV') && (tg.id == self.id)) break;
+                    tg = (tg.parentNode) ? tg.parentNode : false;
+                  }
+                }
+
+                self.cancelScroll();
+
+                tg = self.getTarget(e);
+
+                if (tg) {
+                  var skp = (/INPUT/i.test(tg.nodeName)) && (/range/i.test(tg.type));
+                  if (skp) return self.stopPropagation(e);
+                }
+
+                if (!("clientX" in e) && ("changedTouches" in e)) {
+                  e.clientX = e.changedTouches[0].clientX;
+                  e.clientY = e.changedTouches[0].clientY;
+                }
+
+                if (self.forcescreen) {
+                  var le = e;
+                  e = {
+                    "original": (e.original) ? e.original : e
+                  };
+                  e.clientX = le.screenX;
+                  e.clientY = le.screenY;
+                }
+
+                self.rail.drag = {
+                  x: e.clientX,
+                  y: e.clientY,
+                  sx: self.scroll.x,
+                  sy: self.scroll.y,
+                  st: self.getScrollTop(),
+                  sl: self.getScrollLeft(),
+                  pt: 2,
+                  dl: false
+                };
+
+                if (self.ispage || !self.opt.directionlockdeadzone) {
+                  self.rail.drag.dl = "f";
+                } else {
+
+                  var view = {
+                    w: $(window).width(),
+                    h: $(window).height()
+                  };
+
+                  var page = {
+                    w: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth),
+                    h: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
+                  };
+
+                  var maxh = Math.max(0, page.h - view.h);
+                  var maxw = Math.max(0, page.w - view.w);
+
+                  if (!self.rail.scrollable && self.railh.scrollable) self.rail.drag.ck = (maxh > 0) ? "v" : false;
+                  else if (self.rail.scrollable && !self.railh.scrollable) self.rail.drag.ck = (maxw > 0) ? "h" : false;
+                  else self.rail.drag.ck = false;
+                  if (!self.rail.drag.ck) self.rail.drag.dl = "f";
+                }
+
+                if (self.opt.touchbehavior && self.isiframe && cap.isie) {
+                  var wp = self.win.position();
+                  self.rail.drag.x += wp.left;
+                  self.rail.drag.y += wp.top;
+                }
+
+                self.hasmoving = false;
+                self.lastmouseup = false;
+                self.scrollmom.reset(e.clientX, e.clientY);
+                
+                if (!cap.cantouch && !this.istouchcapable && !e.pointerType) {       
+                
+                  var ip = (tg) ? /INPUT|SELECT|TEXTAREA/i.test(tg.nodeName) : false;
+                  if (!ip) {
+                    if (!self.ispage && cap.hasmousecapture) tg.setCapture();
+                    if (self.opt.touchbehavior) {
+                      if (tg.onclick && !(tg._onclick || false)) { // intercept DOM0 onclick event
+                        tg._onclick = tg.onclick;
+                        tg.onclick = function(e) {
+                          if (self.hasmoving) return false;
+                          tg._onclick.call(this, e);
+                        };
+                      }
+                      return self.cancelEvent(e);
+                    }
+                    return self.stopPropagation(e);
+                  }
+
+                  if (/SUBMIT|CANCEL|BUTTON/i.test($(tg).attr('type'))) {
+                    pc = {
+                      "tg": tg,
+                      "click": false
+                    };
+                    self.preventclick = pc;
+                  }
+
+                }
+              }
+
+            };
+
+            self.ontouchend = function(e) {              
+              if (!self.rail.drag) return true;              
+              if (self.rail.drag.pt == 2) {
+                if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false;
+                self.scrollmom.doMomentum();
+                self.rail.drag = false;
+                if (self.hasmoving) {
+                  self.lastmouseup = true;
+                  self.hideCursor();
+                  if (cap.hasmousecapture) document.releaseCapture();
+                  if (!cap.cantouch) return self.cancelEvent(e);
+                }
+              }
+              else if (self.rail.drag.pt == 1) {
+                return self.onmouseup(e);
+              }
+
+            };
+
+            var moveneedoffset = (self.opt.touchbehavior && self.isiframe && !cap.hasmousecapture);
+
+            self.ontouchmove = function(e, byiframe) {
+
+              if (!self.rail.drag) return false;
+            
+              if (e.targetTouches && self.opt.preventmultitouchscrolling) {
+                if (e.targetTouches.length > 1) return false; // multitouch
+              }
+            
+              if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false;
+          
+              if (self.rail.drag.pt == 2) {
+                if (cap.cantouch && (cap.isios) && (typeof e.original == "undefined")) return true; // prevent ios "ghost" events by clickable elements
+
+                self.hasmoving = true;
+
+                if (self.preventclick && !self.preventclick.click) {
+                  self.preventclick.click = self.preventclick.tg.onclick || false;
+                  self.preventclick.tg.onclick = self.onpreventclick;
+                }
+
+                var ev = $.extend({
+                  "original": e
+                }, e);
+                e = ev;
+
+                if (("changedTouches" in e)) {
+                  e.clientX = e.changedTouches[0].clientX;
+                  e.clientY = e.changedTouches[0].clientY;
+                }
+
+                if (self.forcescreen) {
+                  var le = e;
+                  e = {
+                    "original": (e.original) ? e.original : e
+                  };
+                  e.clientX = le.screenX;
+                  e.clientY = le.screenY;
+                }
+
+                var ofy,ofx;
+                ofx = ofy = 0;
+
+                if (moveneedoffset && !byiframe) {
+                  var wp = self.win.position();
+                  ofx = -wp.left;
+                  ofy = -wp.top;
+                }
+
+                var fy = e.clientY + ofy;
+                var my = (fy - self.rail.drag.y);
+                var fx = e.clientX + ofx;
+                var mx = (fx - self.rail.drag.x);
+
+                var ny = self.rail.drag.st - my;
+
+                if (self.ishwscroll && self.opt.bouncescroll) {
+                  if (ny < 0) {
+                    ny = Math.round(ny / 2);
+                    //                    fy = 0;
+                  } else if (ny > self.page.maxh) {
+                    ny = self.page.maxh + Math.round((ny - self.page.maxh) / 2);
+                    //                    fy = 0;
+                  }
+                } else {
+                  if (ny < 0) {
+                    ny = 0;
+                    fy = 0;
+                  }
+                  if (ny > self.page.maxh) {
+                    ny = self.page.maxh;
+                    fy = 0;
+                  }
+                }
+
+                var nx;
+                if (self.railh && self.railh.scrollable) {
+                  nx = (self.isrtlmode) ? mx - self.rail.drag.sl : self.rail.drag.sl - mx;
+
+                  if (self.ishwscroll && self.opt.bouncescroll) {
+                    if (nx < 0) {
+                      nx = Math.round(nx / 2);
+                      //                      fx = 0;
+                    } else if (nx > self.page.maxw) {
+                      nx = self.page.maxw + Math.round((nx - self.page.maxw) / 2);
+                      //                      fx = 0;
+                    }
+                  } else {
+                    if (nx < 0) {
+                      nx = 0;
+                      fx = 0;
+                    }
+                    if (nx > self.page.maxw) {
+                      nx = self.page.maxw;
+                      fx = 0;
+                    }
+                  }
+
+                }
+
+                var grabbed = false;
+                if (self.rail.drag.dl) {
+                  grabbed = true;
+                  if (self.rail.drag.dl == "v") nx = self.rail.drag.sl;
+                  else if (self.rail.drag.dl == "h") ny = self.rail.drag.st;
+                } else {
+                  var ay = Math.abs(my);
+                  var ax = Math.abs(mx);
+                  var dz = self.opt.directionlockdeadzone;
+                  if (self.rail.drag.ck == "v") {
+                    if (ay > dz && (ax <= (ay * 0.3))) {
+                      self.rail.drag = false;
+                      return true;
+                    } else if (ax > dz) {
+                      self.rail.drag.dl = "f";
+                      $("body").scrollTop($("body").scrollTop()); // stop iOS native scrolling (when active javascript has blocked)
+                    }
+                  } else if (self.rail.drag.ck == "h") {
+                    if (ax > dz && (ay <= (ax * 0.3))) {
+                      self.rail.drag = false;
+                      return true;
+                    } else if (ay > dz) {
+                      self.rail.drag.dl = "f";
+                      $("body").scrollLeft($("body").scrollLeft()); // stop iOS native scrolling (when active javascript has blocked)
+                    }
+                  }
+                }
+
+                self.synched("touchmove", function() {
+                  if (self.rail.drag && (self.rail.drag.pt == 2)) {
+                    if (self.prepareTransition) self.prepareTransition(0);
+                    if (self.rail.scrollable) self.setScrollTop(ny);
+                    self.scrollmom.update(fx, fy);
+                    if (self.railh && self.railh.scrollable) {
+                      self.setScrollLeft(nx);
+                      self.showCursor(ny, nx);
+                    } else {
+                      self.showCursor(ny);
+                    }
+                    if (cap.isie10) document.selection.clear();
+                  }
+                });
+
+                if (cap.ischrome && self.istouchcapable) grabbed = false; //chrome touch emulation doesn't like!
+                if (grabbed) return self.cancelEvent(e);
+              }
+              else if (self.rail.drag.pt == 1) { // drag on cursor
+                return self.onmousemove(e);
+              }
+
+            };
+
+          }
+
+          self.onmousedown = function(e, hronly) {
+            if (self.rail.drag && self.rail.drag.pt != 1) return;
+            if (self.railslocked) return self.cancelEvent(e);
+            self.cancelScroll();
+            self.rail.drag = {
+              x: e.clientX,
+              y: e.clientY,
+              sx: self.scroll.x,
+              sy: self.scroll.y,
+              pt: 1,
+              hr: (!!hronly)
+            };
+            var tg = self.getTarget(e);
+            if (!self.ispage && cap.hasmousecapture) tg.setCapture();
+            if (self.isiframe && !cap.hasmousecapture) {
+              self.saved.csspointerevents = self.doc.css("pointer-events");
+              self.css(self.doc, {
+                "pointer-events": "none"
+              });
+            }
+            self.hasmoving = false;
+            return self.cancelEvent(e);
+          };
+
+          self.onmouseup = function(e) {
+            if (self.rail.drag) {
+              if (self.rail.drag.pt != 1) return true;
+              if (cap.hasmousecapture) document.releaseCapture();
+              if (self.isiframe && !cap.hasmousecapture) self.doc.css("pointer-events", self.saved.csspointerevents);              
+              self.rail.drag = false;
+              //if (!self.rail.active) self.hideCursor();
+              if (self.hasmoving) self.triggerScrollEnd(); // TODO - check &&!self.scrollrunning
+              return self.cancelEvent(e);
+            }
+          };
+
+          self.onmousemove = function(e) {
+            if (self.rail.drag) {
+              if (self.rail.drag.pt != 1) return;
+
+              if (cap.ischrome && e.which == 0) return self.onmouseup(e);
+
+              self.cursorfreezed = true;
+              self.hasmoving = true;
+
+              if (self.rail.drag.hr) {
+                self.scroll.x = self.rail.drag.sx + (e.clientX - self.rail.drag.x);
+                if (self.scroll.x < 0) self.scroll.x = 0;
+                var mw = self.scrollvaluemaxw;
+                if (self.scroll.x > mw) self.scroll.x = mw;
+              } else {
+                self.scroll.y = self.rail.drag.sy + (e.clientY - self.rail.drag.y);
+                if (self.scroll.y < 0) self.scroll.y = 0;
+                var my = self.scrollvaluemax;
+                if (self.scroll.y > my) self.scroll.y = my;
+              }
+
+              self.synched('mousemove', function() {
+                if (self.rail.drag && (self.rail.drag.pt == 1)) {
+                  self.showCursor();
+                  if (self.rail.drag.hr) {
+                    if (self.hasreversehr) {
+                      self.doScrollLeft(self.scrollvaluemaxw-Math.round(self.scroll.x * self.scrollratio.x), self.opt.cursordragspeed);
+                    } else {
+                      self.doScrollLeft(Math.round(self.scroll.x * self.scrollratio.x), self.opt.cursordragspeed);
+                    }
+                  }
+                  else self.doScrollTop(Math.round(self.scroll.y * self.scrollratio.y), self.opt.cursordragspeed);
+                }
+              });
+
+              return self.cancelEvent(e);
+            }
+            /*              
+            else {
+              self.checkarea = true;
+            }
+*/
+          };
+
+          if (cap.cantouch || self.opt.touchbehavior) {
+
+            self.onpreventclick = function(e) {
+              if (self.preventclick) {
+                self.preventclick.tg.onclick = self.preventclick.click;
+                self.preventclick = false;
+                return self.cancelEvent(e);
+              }
+            }
+
+            self.bind(self.win, "mousedown", self.ontouchstart); // control content dragging
+
+            self.onclick = (cap.isios) ? false : function(e) {
+              if (self.lastmouseup) {
+                self.lastmouseup = false;
+                return self.cancelEvent(e);
+              } else {
+                return true;
+              }
+            };
+
+            if (self.opt.grabcursorenabled && cap.cursorgrabvalue) {
+              self.css((self.ispage) ? self.doc : self.win, {
+                'cursor': cap.cursorgrabvalue
+              });
+              self.css(self.rail, {
+                'cursor': cap.cursorgrabvalue
+              });
+            }
+
+          } else {
+
+            var checkSelectionScroll = function(e) {
+              if (!self.selectiondrag) return;
+
+              if (e) {
+                var ww = self.win.outerHeight();
+                var df = (e.pageY - self.selectiondrag.top);
+                if (df > 0 && df < ww) df = 0;
+                if (df >= ww) df -= ww;
+                self.selectiondrag.df = df;
+              }
+              if (self.selectiondrag.df == 0) return;
+
+              var rt = -Math.floor(self.selectiondrag.df / 6) * 2;
+              self.doScrollBy(rt);
+
+              self.debounced("doselectionscroll", function() {
+                checkSelectionScroll()
+              }, 50);
+            };
+
+            if ("getSelection" in document) { // A grade - Major browsers
+              self.hasTextSelected = function() {
+                return (document.getSelection().rangeCount > 0);
+              };
+            } else if ("selection" in document) { //IE9-
+              self.hasTextSelected = function() {
+                return (document.selection.type != "None");
+              };
+            } else {
+              self.hasTextSelected = function() { // no support
+                return false;
+              };
+            }
+
+            self.onselectionstart = function(e) {
+/*  More testing - severe chrome issues            
+              if (!self.haswrapper&&(e.which&&e.which==2)) {  // fool browser to manage middle button scrolling
+                self.win.css({'overflow':'auto'});
+                setTimeout(function(){
+                  self.win.css({'overflow':''});
+                },10);                
+                return true;
+              }            
+*/              
+              if (self.ispage) return;
+              self.selectiondrag = self.win.offset();
+            };
+            
+            self.onselectionend = function(e) {
+              self.selectiondrag = false;
+            };
+            self.onselectiondrag = function(e) {
+              if (!self.selectiondrag) return;
+              if (self.hasTextSelected()) self.debounced("selectionscroll", function() {
+                checkSelectionScroll(e)
+              }, 250);
+            };
+
+
+          }
+
+          if (cap.hasw3ctouch) { //IE11+
+            self.css(self.rail, {
+              'touch-action': 'none'
+            });
+            self.css(self.cursor, {
+              'touch-action': 'none'
+            });
+            self.bind(self.win, "pointerdown", self.ontouchstart);
+            self.bind(document, "pointerup", self.ontouchend);
+            self.bind(document, "pointermove", self.ontouchmove);
+          } else if (cap.hasmstouch) { //IE10
+            self.css(self.rail, {
+              '-ms-touch-action': 'none'
+            });
+            self.css(self.cursor, {
+              '-ms-touch-action': 'none'
+            });
+            self.bind(self.win, "MSPointerDown", self.ontouchstart);
+            self.bind(document, "MSPointerUp", self.ontouchend);
+            self.bind(document, "MSPointerMove", self.ontouchmove);
+            self.bind(self.cursor, "MSGestureHold", function(e) {
+              e.preventDefault()
+            });
+            self.bind(self.cursor, "contextmenu", function(e) {
+              e.preventDefault()
+            });
+          } else if (this.istouchcapable) { //desktop with screen touch enabled
+            self.bind(self.win, "touchstart", self.ontouchstart);
+            self.bind(document, "touchend", self.ontouchend);
+            self.bind(document, "touchcancel", self.ontouchend);
+            self.bind(document, "touchmove", self.ontouchmove);
+          }
+
+          
+          if (self.opt.cursordragontouch || (!cap.cantouch && !self.opt.touchbehavior)) {
+
+            self.rail.css({
+              "cursor": "default"
+            });
+            self.railh && self.railh.css({
+              "cursor": "default"
+            });
+
+            self.jqbind(self.rail, "mouseenter", function() {
+              if (!self.ispage && !self.win.is(":visible")) return false;
+              if (self.canshowonmouseevent) self.showCursor();
+              self.rail.active = true;
+            });
+            self.jqbind(self.rail, "mouseleave", function() {
+              self.rail.active = false;
+              if (!self.rail.drag) self.hideCursor();
+            });
+
+            if (self.opt.sensitiverail) {
+              self.bind(self.rail, "click", function(e) {
+                self.doRailClick(e, false, false)
+              });
+              self.bind(self.rail, "dblclick", function(e) {
+                self.doRailClick(e, true, false)
+              });
+              self.bind(self.cursor, "click", function(e) {
+                self.cancelEvent(e)
+              });
+              self.bind(self.cursor, "dblclick", function(e) {
+                self.cancelEvent(e)
+              });
+            }
+
+            if (self.railh) {
+              self.jqbind(self.railh, "mouseenter", function() {
+                if (!self.ispage && !self.win.is(":visible")) return false;
+                if (self.canshowonmouseevent) self.showCursor();
+                self.rail.active = true;
+              });
+              self.jqbind(self.railh, "mouseleave", function() {
+                self.rail.active = false;
+                if (!self.rail.drag) self.hideCursor();
+              });
+
+              if (self.opt.sensitiverail) {
+                self.bind(self.railh, "click", function(e) {
+                  self.doRailClick(e, false, true)
+                });
+                self.bind(self.railh, "dblclick", function(e) {
+                  self.doRailClick(e, true, true)
+                });
+                self.bind(self.cursorh, "click", function(e) {
+                  self.cancelEvent(e)
+                });
+                self.bind(self.cursorh, "dblclick", function(e) {
+                  self.cancelEvent(e)
+                });
+              }
+
+            }
+
+          }
+
+          if (!cap.cantouch && !self.opt.touchbehavior) {
+
+            self.bind((cap.hasmousecapture) ? self.win : document, "mouseup", self.onmouseup);
+            self.bind(document, "mousemove", self.onmousemove);
+            if (self.onclick) self.bind(document, "click", self.onclick);
+
+            self.bind(self.cursor, "mousedown", self.onmousedown);
+            self.bind(self.cursor, "mouseup", self.onmouseup);
+
+            if (self.railh) {
+              self.bind(self.cursorh, "mousedown", function(e) {
+                self.onmousedown(e, true)
+              });
+              self.bind(self.cursorh, "mouseup", self.onmouseup);
+            }
+            
+            if (!self.ispage && self.opt.enablescrollonselection) {
+              self.bind(self.win[0], "mousedown", self.onselectionstart);
+              self.bind(document, "mouseup", self.onselectionend);
+              self.bind(self.cursor, "mouseup", self.onselectionend);
+              if (self.cursorh) self.bind(self.cursorh, "mouseup", self.onselectionend);
+              self.bind(document, "mousemove", self.onselectiondrag);
+            }
+
+            if (self.zoom) {
+              self.jqbind(self.zoom, "mouseenter", function() {
+                if (self.canshowonmouseevent) self.showCursor();
+                self.rail.active = true;
+              });
+              self.jqbind(self.zoom, "mouseleave", function() {
+                self.rail.active = false;
+                if (!self.rail.drag) self.hideCursor();
+              });
+            }
+
+          } else {
+
+            self.bind((cap.hasmousecapture) ? self.win : document, "mouseup", self.ontouchend);
+            self.bind(document, "mousemove", self.ontouchmove);
+            if (self.onclick) self.bind(document, "click", self.onclick);
+
+            if (self.opt.cursordragontouch) {
+              self.bind(self.cursor, "mousedown", self.onmousedown);
+              self.bind(self.cursor, "mouseup", self.onmouseup);
+              //self.bind(self.cursor, "mousemove", self.onmousemove);
+              self.cursorh && self.bind(self.cursorh, "mousedown", function(e) {
+                self.onmousedown(e, true)
+              });
+              //self.cursorh && self.bind(self.cursorh, "mousemove", self.onmousemove);
+              self.cursorh && self.bind(self.cursorh, "mouseup", self.onmouseup);
+            }
+
+          }
+
+          if (self.opt.enablemousewheel) {
+            if (!self.isiframe) self.bind((cap.isie && self.ispage) ? document : self.win /*self.docscroll*/ , "mousewheel", self.onmousewheel);
+            self.bind(self.rail, "mousewheel", self.onmousewheel);
+            if (self.railh) self.bind(self.railh, "mousewheel", self.onmousewheelhr);
+          }
+
+          if (!self.ispage && !cap.cantouch && !(/HTML|^BODY/.test(self.win[0].nodeName))) {
+            if (!self.win.attr("tabindex")) self.win.attr({
+              "tabindex": tabindexcounter++
+            });
+
+            self.jqbind(self.win, "focus", function(e) {
+              domfocus = (self.getTarget(e)).id || true;
+              self.hasfocus = true;
+              if (self.canshowonmouseevent) self.noticeCursor();
+            });
+            self.jqbind(self.win, "blur", function(e) {
+              domfocus = false;
+              self.hasfocus = false;
+            });
+
+            self.jqbind(self.win, "mouseenter", function(e) {
+              mousefocus = (self.getTarget(e)).id || true;
+              self.hasmousefocus = true;
+              if (self.canshowonmouseevent) self.noticeCursor();
+            });
+            self.jqbind(self.win, "mouseleave", function() {
+              mousefocus = false;
+              self.hasmousefocus = false;
+              if (!self.rail.drag) self.hideCursor();
+            });
+
+          }
+
+        } // !ie9mobile
+
+        //Thanks to http://www.quirksmode.org !!
+        self.onkeypress = function(e) {
+          if (self.railslocked && self.page.maxh == 0) return true;
+
+          e = (e) ? e : window.e;
+          var tg = self.getTarget(e);
+          if (tg && /INPUT|TEXTAREA|SELECT|OPTION/.test(tg.nodeName)) {
+            var tp = tg.getAttribute('type') || tg.type || false;
+            if ((!tp) || !(/submit|button|cancel/i.tp)) return true;
+          }
+
+          if ($(tg).attr('contenteditable')) return true;
+
+          if (self.hasfocus || (self.hasmousefocus && !domfocus) || (self.ispage && !domfocus && !mousefocus)) {
+            var key = e.keyCode;
+
+            if (self.railslocked && key != 27) return self.cancelEvent(e);
+
+            var ctrl = e.ctrlKey || false;
+            var shift = e.shiftKey || false;
+
+            var ret = false;
+            switch (key) {
+              case 38:
+              case 63233: //safari
+                self.doScrollBy(24 * 3);
+                ret = true;
+                break;
+              case 40:
+              case 63235: //safari
+                self.doScrollBy(-24 * 3);
+                ret = true;
+                break;
+              case 37:
+              case 63232: //safari
+                if (self.railh) {
+                  (ctrl) ? self.doScrollLeft(0): self.doScrollLeftBy(24 * 3);
+                  ret = true;
+                }
+                break;
+              case 39:
+              case 63234: //safari
+                if (self.railh) {
+                  (ctrl) ? self.doScrollLeft(self.page.maxw): self.doScrollLeftBy(-24 * 3);
+                  ret = true;
+                }
+                break;
+              case 33:
+              case 63276: // safari
+                self.doScrollBy(self.view.h);
+                ret = true;
+                break;
+              case 34:
+              case 63277: // safari
+                self.doScrollBy(-self.view.h);
+                ret = true;
+                break;
+              case 36:
+              case 63273: // safari                
+                (self.railh && ctrl) ? self.doScrollPos(0, 0): self.doScrollTo(0);
+                ret = true;
+                break;
+              case 35:
+              case 63275: // safari
+                (self.railh && ctrl) ? self.doScrollPos(self.page.maxw, self.page.maxh): self.doScrollTo(self.page.maxh);
+                ret = true;
+                break;
+              case 32:
+                if (self.opt.spacebarenabled) {
+                  (shift) ? self.doScrollBy(self.view.h): self.doScrollBy(-self.view.h);
+                  ret = true;
+                }
+                break;
+              case 27: // ESC
+                if (self.zoomactive) {
+                  self.doZoom();
+                  ret = true;
+                }
+                break;
+            }
+            if (ret) return self.cancelEvent(e);
+          }
+        };
+
+        if (self.opt.enablekeyboard) self.bind(document, (cap.isopera && !cap.isopera12) ? "keypress" : "keydown", self.onkeypress);
+
+        self.bind(document, "keydown", function(e) {
+          var ctrl = e.ctrlKey || false;
+          if (ctrl) self.wheelprevented = true;
+        });
+        self.bind(document, "keyup", function(e) {
+          var ctrl = e.ctrlKey || false;
+          if (!ctrl) self.wheelprevented = false;
+        });
+        self.bind(window,"blur",function(e){
+          self.wheelprevented = false;
+        });        
+
+        self.bind(window, 'resize', self.lazyResize);
+        self.bind(window, 'orientationchange', self.lazyResize);
+
+        self.bind(window, "load", self.lazyResize);
+
+        if (cap.ischrome && !self.ispage && !self.haswrapper) { //chrome void scrollbar bug - it persists in version 26
+          var tmp = self.win.attr("style");
+          var ww = parseFloat(self.win.css("width")) + 1;
+          self.win.css('width', ww);
+          self.synched("chromefix", function() {
+            self.win.attr("style", tmp)
+          });
+        }
+
+
+        // Trying a cross-browser implementation - good luck!
+
+        self.onAttributeChange = function(e) {
+          self.lazyResize(self.isieold ? 250 : 30);
+        };
+
+        if (ClsMutationObserver !== false) {
+          self.observerbody = new ClsMutationObserver(function(mutations) {
+            mutations.forEach(function(mut){
+              if (mut.type=="attributes") {
+                return ($("body").hasClass("modal-open")) ? self.hide() : self.show();  // Support for Bootstrap modal
+              }
+            });  
+            if (document.body.scrollHeight!=self.page.maxh) return self.lazyResize(30);
+          });
+          self.observerbody.observe(document.body, {
+            childList: true,
+            subtree: true,
+            characterData: false,
+            attributes: true,
+            attributeFilter: ['class']
+          });
+        }
+        
+        if (!self.ispage && !self.haswrapper) {
+          // redesigned MutationObserver for Chrome18+/Firefox14+/iOS6+ with support for: remove div, add/remove content
+          if (ClsMutationObserver !== false) {
+            self.observer = new ClsMutationObserver(function(mutations) {
+              mutations.forEach(self.onAttributeChange);
+            });
+            self.observer.observe(self.win[0], {
+              childList: true,
+              characterData: false,
+              attributes: true,
+              subtree: false
+            });
+            self.observerremover = new ClsMutationObserver(function(mutations) {
+              mutations.forEach(function(mo) {
+                if (mo.removedNodes.length > 0) {
+                  for (var dd in mo.removedNodes) {
+                    if (!!self && (mo.removedNodes[dd] == self.win[0])) return self.remove();
+                  }
+                }
+              });
+            });
+            self.observerremover.observe(self.win[0].parentNode, {
+              childList: true,
+              characterData: false,
+              attributes: false,
+              subtree: false
+            });
+          } else {
+            self.bind(self.win, (cap.isie && !cap.isie9) ? "propertychange" : "DOMAttrModified", self.onAttributeChange);
+            if (cap.isie9) self.win[0].attachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug
+            self.bind(self.win, "DOMNodeRemoved", function(e) {
+              if (e.target == self.win[0]) self.remove();
+            });
+          }
+        }
+
+        //
+
+        if (!self.ispage && self.opt.boxzoom) self.bind(window, "resize", self.resizeZoom);
+        if (self.istextarea) self.bind(self.win, "mouseup", self.lazyResize);
+
+        //        self.checkrtlmode = true;
+        self.lazyResize(30);
+
+      }
+      
+      if (this.doc[0].nodeName == 'IFRAME') {
+        var oniframeload = function() {
+          self.iframexd = false;
+          var doc;
+          try {
+            doc = 'contentDocument' in this ? this.contentDocument : this.contentWindow.document;
+            var a = doc.domain;
+          } catch (e) {
+            self.iframexd = true;
+            doc = false
+          }
+          
+          if (self.iframexd) {
+            if ("console" in window) console.log('NiceScroll error: policy restriced iframe');
+            return true; //cross-domain - I can't manage this        
+          }
+
+          self.forcescreen = true;
+
+          if (self.isiframe) {
+            self.iframe = {
+              "doc": $(doc),
+              "html": self.doc.contents().find('html')[0],
+              "body": self.doc.contents().find('body')[0]
+            };
+            self.getContentSize = function() {
+              return {
+                w: Math.max(self.iframe.html.scrollWidth, self.iframe.body.scrollWidth),
+                h: Math.max(self.iframe.html.scrollHeight, self.iframe.body.scrollHeight)
+              };
+            };
+            self.docscroll = $(self.iframe.body); //$(this.contentWindow);
+          }
+
+          if (!cap.isios && self.opt.iframeautoresize && !self.isiframe) {
+            self.win.scrollTop(0); // reset position
+            self.doc.height(""); //reset height to fix browser bug
+            var hh = Math.max(doc.getElementsByTagName('html')[0].scrollHeight, doc.body.scrollHeight);
+            self.doc.height(hh);
+          }
+          self.lazyResize(30);
+
+          if (cap.isie7) self.css($(self.iframe.html), {
+            'overflow-y': 'hidden'
+          });
+          self.css($(self.iframe.body), {
+            'overflow-y': 'hidden'
+          });
+
+          if (cap.isios && self.haswrapper) {
+            self.css($(doc.body), {
+              '-webkit-transform': 'translate3d(0,0,0)'
+            }); // avoid iFrame content clipping - thanks to http://blog.derraab.com/2012/04/02/avoid-iframe-content-clipping-with-css-transform-on-ios/
+          }
+
+          if ('contentWindow' in this) {
+            self.bind(this.contentWindow, "scroll", self.onscroll); //IE8 & minor
+          } else {
+            self.bind(doc, "scroll", self.onscroll);
+          }
+
+          if (self.opt.enablemousewheel) {
+            self.bind(doc, "mousewheel", self.onmousewheel);
+          }
+
+          if (self.opt.enablekeyboard) self.bind(doc, (cap.isopera) ? "keypress" : "keydown", self.onkeypress);
+
+          if (cap.cantouch || self.opt.touchbehavior) {
+            self.bind(doc, "mousedown", self.ontouchstart);
+            self.bind(doc, "mousemove", function(e) {
+              return self.ontouchmove(e, true)
+            });
+            if (self.opt.grabcursorenabled && cap.cursorgrabvalue) self.css($(doc.body), {
+              'cursor': cap.cursorgrabvalue
+            });
+          }
+
+          self.bind(doc, "mouseup", self.ontouchend);
+
+          if (self.zoom) {
+            if (self.opt.dblclickzoom) self.bind(doc, 'dblclick', self.doZoom);
+            if (self.ongesturezoom) self.bind(doc, "gestureend", self.ongesturezoom);
+          }
+        };
+
+        if (this.doc[0].readyState && this.doc[0].readyState == "complete") {
+          setTimeout(function() {
+            oniframeload.call(self.doc[0], false)
+          }, 500);
+        }
+        self.bind(this.doc, "load", oniframeload);
+
+      }
+
+    };
+
+    this.showCursor = function(py, px) {
+      if (self.cursortimeout) {
+        clearTimeout(self.cursortimeout);
+        self.cursortimeout = 0;
+      }
+      if (!self.rail) return;
+      if (self.autohidedom) {
+        self.autohidedom.stop().css({
+          opacity: self.opt.cursoropacitymax
+        });
+        self.cursoractive = true;
+      }
+
+      if (!self.rail.drag || self.rail.drag.pt != 1) {
+        if ((typeof py != "undefined") && (py !== false)) {
+          self.scroll.y = Math.round(py * 1 / self.scrollratio.y);
+        }
+        if (typeof px != "undefined") {
+          self.scroll.x = Math.round(px * 1 / self.scrollratio.x);
+        }
+      }
+
+      self.cursor.css({
+        height: self.cursorheight,
+        top: self.scroll.y
+      });
+      if (self.cursorh) {        
+        var lx = (self.hasreversehr) ? self.scrollvaluemaxw-self.scroll.x : self.scroll.x;
+        (!self.rail.align && self.rail.visibility) ? self.cursorh.css({
+          width: self.cursorwidth,
+          left: lx + self.rail.width
+        }): self.cursorh.css({
+          width: self.cursorwidth,
+          left: lx
+        });
+        self.cursoractive = true;
+      }
+
+      if (self.zoom) self.zoom.stop().css({
+        opacity: self.opt.cursoropacitymax
+      });
+    };
+
+    this.hideCursor = function(tm) {
+      if (self.cursortimeout) return;
+      if (!self.rail) return;
+      if (!self.autohidedom) return;
+      if (self.hasmousefocus && self.opt.autohidemode == "leave") return;
+      self.cursortimeout = setTimeout(function() {
+        if (!self.rail.active || !self.showonmouseevent) {
+          self.autohidedom.stop().animate({
+            opacity: self.opt.cursoropacitymin
+          });
+          if (self.zoom) self.zoom.stop().animate({
+            opacity: self.opt.cursoropacitymin
+          });
+          self.cursoractive = false;
+        }
+        self.cursortimeout = 0;
+      }, tm || self.opt.hidecursordelay);
+    };
+
+    this.noticeCursor = function(tm, py, px) {
+      self.showCursor(py, px);
+      if (!self.rail.active) self.hideCursor(tm);
+    };
+
+    this.getContentSize =
+      (self.ispage) ?
+      function() {
+        return {
+          w: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth),
+          h: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
+        }
+      } : (self.haswrapper) ?
+      function() {
+        return {
+          w: self.doc.outerWidth() + parseInt(self.win.css('paddingLeft')) + parseInt(self.win.css('paddingRight')),
+          h: self.doc.outerHeight() + parseInt(self.win.css('paddingTop')) + parseInt(self.win.css('paddingBottom'))
+        }
+      } : function() {
+        return {
+          w: self.docscroll[0].scrollWidth,
+          h: self.docscroll[0].scrollHeight
+        }
+      };
+
+    this.onResize = function(e, page) {
+    
+      if (!self || !self.win) return false;
+
+      if (!self.haswrapper && !self.ispage) {
+        if (self.win.css('display') == 'none') {
+          if (self.visibility) self.hideRail().hideRailHr();
+          return false;
+        } else {
+          if (!self.hidden && !self.visibility) self.showRail().showRailHr();
+        }
+      }
+
+      var premaxh = self.page.maxh;
+      var premaxw = self.page.maxw;
+
+      var preview = {
+        h: self.view.h,
+        w: self.view.w
+      };
+
+      self.view = {
+        w: (self.ispage) ? self.win.width() : parseInt(self.win[0].clientWidth),
+        h: (self.ispage) ? self.win.height() : parseInt(self.win[0].clientHeight)
+      };
+
+      self.page = (page) ? page : self.getContentSize();
+
+      self.page.maxh = Math.max(0, self.page.h - self.view.h);
+      self.page.maxw = Math.max(0, self.page.w - self.view.w);
+      
+      if ((self.page.maxh == premaxh) && (self.page.maxw == premaxw) && (self.view.w == preview.w) && (self.view.h == preview.h)) {
+        // test position        
+        if (!self.ispage) {
+          var pos = self.win.offset();
+          if (self.lastposition) {
+            var lst = self.lastposition;
+            if ((lst.top == pos.top) && (lst.left == pos.left)) return self; //nothing to do            
+          }
+          self.lastposition = pos;
+        } else {
+          return self; //nothing to do
+        }
+      }
+
+      if (self.page.maxh == 0) {
+        self.hideRail();
+        self.scrollvaluemax = 0;
+        self.scroll.y = 0;
+        self.scrollratio.y = 0;
+        self.cursorheight = 0;
+        self.setScrollTop(0);
+        self.rail.scrollable = false;
+      } else {
+        self.page.maxh -= (self.opt.railpadding.top + self.opt.railpadding.bottom);  //**
+        self.rail.scrollable = true;
+      }
+
+      if (self.page.maxw == 0) {
+        self.hideRailHr();
+        self.scrollvaluemaxw = 0;
+        self.scroll.x = 0;
+        self.scrollratio.x = 0;
+        self.cursorwidth = 0;
+        self.setScrollLeft(0);
+        self.railh.scrollable = false;
+      } else {
+        self.page.maxw -= (self.opt.railpadding.left + self.opt.railpadding.right);  //**
+        self.railh.scrollable = true;
+      }
+
+      self.railslocked = (self.locked) || ((self.page.maxh == 0) && (self.page.maxw == 0));
+      if (self.railslocked) {
+        if (!self.ispage) self.updateScrollBar(self.view);
+        return false;
+      }
+
+      if (!self.hidden && !self.visibility) {
+        self.showRail().showRailHr();
+      }
+      else if (!self.hidden && !self.railh.visibility) self.showRailHr();
+
+      if (self.istextarea && self.win.css('resize') && self.win.css('resize') != 'none') self.view.h -= 20;
+
+      self.cursorheight = Math.min(self.view.h, Math.round(self.view.h * (self.view.h / self.page.h)));
+      self.cursorheight = (self.opt.cursorfixedheight) ? self.opt.cursorfixedheight : Math.max(self.opt.cursorminheight, self.cursorheight);
+
+      self.cursorwidth = Math.min(self.view.w, Math.round(self.view.w * (self.view.w / self.page.w)));
+      self.cursorwidth = (self.opt.cursorfixedheight) ? self.opt.cursorfixedheight : Math.max(self.opt.cursorminheight, self.cursorwidth);
+
+      self.scrollvaluemax = self.view.h - self.cursorheight - self.cursor.hborder - (self.opt.railpadding.top + self.opt.railpadding.bottom);  //**
+
+      if (self.railh) {
+        self.railh.width = (self.page.maxh > 0) ? (self.view.w - self.rail.width) : self.view.w;
+        self.scrollvaluemaxw = self.railh.width - self.cursorwidth - self.cursorh.wborder - (self.opt.railpadding.left + self.opt.railpadding.right);  //**
+      }
+
+      /*
+      if (self.checkrtlmode&&self.railh) {
+        self.checkrtlmode = false;
+        if (self.opt.rtlmode&&self.scroll.x==0) self.setScrollLeft(self.page.maxw);
+      }
+*/
+
+      if (!self.ispage) self.updateScrollBar(self.view);
+
+      self.scrollratio = {
+        x: (self.page.maxw / self.scrollvaluemaxw),
+        y: (self.page.maxh / self.scrollvaluemax)
+      };
+
+      var sy = self.getScrollTop();
+      if (sy > self.page.maxh) {
+        self.doScrollTop(self.page.maxh);
+      } else {
+        self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y));
+        self.scroll.x = Math.round(self.getScrollLeft() * (1 / self.scrollratio.x));
+        if (self.cursoractive) self.noticeCursor();
+      }
+
+      if (self.scroll.y && (self.getScrollTop() == 0)) self.doScrollTo(Math.floor(self.scroll.y * self.scrollratio.y));
+
+      return self;
+    };
+
+    this.resize = self.onResize;
+
+    this.lazyResize = function(tm) { // event debounce
+      tm = (isNaN(tm)) ? 30 : tm;
+      self.debounced('resize', self.resize, tm);
+      return self;
+    };
+
+    // modified by MDN https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/wheel
+    function _modernWheelEvent(dom, name, fn, bubble) {
+      self._bind(dom, name, function(e) {
+        var e = (e) ? e : window.event;
+        var event = {
+          original: e,
+          target: e.target || e.srcElement,
+          type: "wheel",
+          deltaMode: e.type == "MozMousePixelScroll" ? 0 : 1,
+          deltaX: 0,
+          deltaZ: 0,
+          preventDefault: function() {
+            e.preventDefault ? e.preventDefault() : e.returnValue = false;
+            return false;
+          },
+          stopImmediatePropagation: function() {
+            (e.stopImmediatePropagation) ? e.stopImmediatePropagation(): e.cancelBubble = true;
+          }
+        };
+
+        if (name == "mousewheel") {
+          event.deltaY = -1 / 40 * e.wheelDelta;
+          e.wheelDeltaX && (event.deltaX = -1 / 40 * e.wheelDeltaX);
+        } else {
+          event.deltaY = e.detail;
+        }
+
+        return fn.call(dom, event);
+      }, bubble);
+    };
+
+
+
+    this.jqbind = function(dom, name, fn) { // use jquery bind for non-native events (mouseenter/mouseleave)
+      self.events.push({
+        e: dom,
+        n: name,
+        f: fn,
+        q: true
+      });
+      $(dom).bind(name, fn);
+    };
+
+    this.bind = function(dom, name, fn, bubble) { // touch-oriented & fixing jquery bind
+      var el = ("jquery" in dom) ? dom[0] : dom;
+
+      if (name == 'mousewheel') {
+        if (window.addEventListener||'onwheel' in document) { // modern brosers & IE9 detection fix
+          self._bind(el, "wheel", fn, bubble || false);
+        } else {
+          var wname = (typeof document.onmousewheel != "undefined") ? "mousewheel" : "DOMMouseScroll"; // older IE/Firefox
+          _modernWheelEvent(el, wname, fn, bubble || false);
+          if (wname == "DOMMouseScroll") _modernWheelEvent(el, "MozMousePixelScroll", fn, bubble || false); // Firefox legacy
+        }
+      } else if (el.addEventListener) {
+        if (cap.cantouch && /mouseup|mousedown|mousemove/.test(name)) { // touch device support
+          var tt = (name == 'mousedown') ? 'touchstart' : (name == 'mouseup') ? 'touchend' : 'touchmove';
+          self._bind(el, tt, function(e) {
+            if (e.touches) {
+              if (e.touches.length < 2) {
+                var ev = (e.touches.length) ? e.touches[0] : e;
+                ev.original = e;
+                fn.call(this, ev);
+              }
+            } else if (e.changedTouches) {
+              var ev = e.changedTouches[0];
+              ev.original = e;
+              fn.call(this, ev);
+            } //blackberry
+          }, bubble || false);
+        }
+        self._bind(el, name, fn, bubble || false);
+        if (cap.cantouch && name == "mouseup") self._bind(el, "touchcancel", fn, bubble || false);
+      } else {
+        self._bind(el, name, function(e) {
+          e = e || window.event || false;
+          if (e) {
+            if (e.srcElement) e.target = e.srcElement;
+          }
+          if (!("pageY" in e)) {
+            e.pageX = e.clientX + document.documentElement.scrollLeft;
+            e.pageY = e.clientY + document.documentElement.scrollTop;
+          }
+          return ((fn.call(el, e) === false) || bubble === false) ? self.cancelEvent(e) : true;
+        });
+      }
+    };
+
+    if (cap.haseventlistener) {  // W3C standard model
+      this._bind = function(el, name, fn, bubble) { // primitive bind
+        self.events.push({
+          e: el,
+          n: name,
+          f: fn,
+          b: bubble,
+          q: false
+        });
+        el.addEventListener(name, fn, bubble || false);
+      };    
+      this.cancelEvent = function(e) {
+        if (!e) return false;
+        var e = (e.original) ? e.original : e;
+        e.preventDefault();
+        e.stopPropagation();
+        if (e.preventManipulation) e.preventManipulation(); //IE10
+        return false;
+      };
+      this.stopPropagation = function(e) {
+        if (!e) return false;
+        var e = (e.original) ? e.original : e;
+        e.stopPropagation();
+        return false;
+      };
+      this._unbind = function(el, name, fn, bub) { // primitive unbind
+        el.removeEventListener(name, fn, bub);
+      };
+    } else {  // old IE model
+      this._bind = function(el, name, fn, bubble) { // primitive bind
+        self.events.push({
+          e: el,
+          n: name,
+          f: fn,
+          b: bubble,
+          q: false
+        });
+        if (el.attachEvent) {
+          el.attachEvent("on" + name, fn);
+        } else {
+          el["on" + name] = fn;
+        }
+      };    
+      // Thanks to http://www.switchonthecode.com !!
+      this.cancelEvent = function(e) {
+        var e = window.event || false;
+        if (!e) return false;
+        e.cancelBubble = true;
+        e.cancel = true;
+        e.returnValue = false;
+        return false;
+      };
+      this.stopPropagation = function(e) {
+        var e = window.event || false;
+        if (!e) return false;
+        e.cancelBubble = true;
+        return false;
+      };
+      this._unbind = function(el, name, fn, bub) { // primitive unbind IE old
+        if (el.detachEvent) {
+          el.detachEvent('on' + name, fn);
+        } else {
+          el['on' + name] = false;
+        }
+      };
+    }
+    
+    this.unbindAll = function() {
+      for (var a = 0; a < self.events.length; a++) {
+        var r = self.events[a];
+        (r.q) ? r.e.unbind(r.n, r.f): self._unbind(r.e, r.n, r.f, r.b);
+      }
+    };
+
+    this.showRail = function() {
+      if ((self.page.maxh != 0) && (self.ispage || self.win.css('display') != 'none')) {
+        self.visibility = true;
+        self.rail.visibility = true;
+        self.rail.css('display', 'block');
+      }
+      return self;
+    };
+
+    this.showRailHr = function() {
+      if (!self.railh) return self;
+      if ((self.page.maxw != 0) && (self.ispage || self.win.css('display') != 'none')) {
+        self.railh.visibility = true;
+        self.railh.css('display', 'block');
+      }
+      return self;
+    };
+
+    this.hideRail = function() {
+      self.visibility = false;
+      self.rail.visibility = false;
+      self.rail.css('display', 'none');
+      return self;
+    };
+
+    this.hideRailHr = function() {
+      if (!self.railh) return self;
+      self.railh.visibility = false;
+      self.railh.css('display', 'none');
+      return self;
+    };
+
+    this.show = function() {
+      self.hidden = false;
+      self.railslocked = false;
+      return self.showRail().showRailHr();
+    };
+
+    this.hide = function() {
+      self.hidden = true;
+      self.railslocked = true;
+      return self.hideRail().hideRailHr();
+    };
+
+    this.toggle = function() {
+      return (self.hidden) ? self.show() : self.hide();
+    };
+
+    this.remove = function() {
+      self.stop();
+      if (self.cursortimeout) clearTimeout(self.cursortimeout);
+      self.doZoomOut();
+      self.unbindAll();
+
+      if (cap.isie9) self.win[0].detachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug
+
+      if (self.observer !== false) self.observer.disconnect();
+      if (self.observerremover !== false) self.observerremover.disconnect();
+      if (self.observerbody !== false) self.observerbody.disconnect();
+
+      self.events = null;
+
+      if (self.cursor) {
+        self.cursor.remove();
+      }
+      if (self.cursorh) {
+        self.cursorh.remove();
+      }
+      if (self.rail) {
+        self.rail.remove();
+      }
+      if (self.railh) {
+        self.railh.remove();
+      }
+      if (self.zoom) {
+        self.zoom.remove();
+      }
+      for (var a = 0; a < self.saved.css.length; a++) {
+        var d = self.saved.css[a];
+        d[0].css(d[1], (typeof d[2] == "undefined") ? '' : d[2]);
+      }
+      self.saved = false;
+      self.me.data('__nicescroll', ''); //erase all traces
+
+      // memory leak fixed by GianlucaGuarini - thanks a lot!
+      // remove the current nicescroll from the $.nicescroll array & normalize array
+      var lst = $.nicescroll;
+      lst.each(function(i) {
+        if (!this) return;
+        if (this.id === self.id) {
+          delete lst[i];
+          for (var b = ++i; b < lst.length; b++, i++) lst[i] = lst[b];
+          lst.length--;
+          if (lst.length) delete lst[lst.length];
+        }
+      });
+
+      for (var i in self) {
+        self[i] = null;
+        delete self[i];
+      }
+
+      self = null;
+
+    };
+
+    this.scrollstart = function(fn) {
+      this.onscrollstart = fn;
+      return self;
+    };
+    this.scrollend = function(fn) {
+      this.onscrollend = fn;
+      return self;
+    };
+    this.scrollcancel = function(fn) {
+      this.onscrollcancel = fn;
+      return self;
+    };
+
+    this.zoomin = function(fn) {
+      this.onzoomin = fn;
+      return self;
+    };
+    this.zoomout = function(fn) {
+      this.onzoomout = fn;
+      return self;
+    };
+
+    this.isScrollable = function(e) {
+      var dom = (e.target) ? e.target : e;
+      if (dom.nodeName == 'OPTION') return true;
+      while (dom && (dom.nodeType == 1) && !(/^BODY|HTML/.test(dom.nodeName))) {
+        var dd = $(dom);
+        var ov = dd.css('overflowY') || dd.css('overflowX') || dd.css('overflow') || '';
+        if (/scroll|auto/.test(ov)) return (dom.clientHeight != dom.scrollHeight);
+        dom = (dom.parentNode) ? dom.parentNode : false;
+      }
+      return false;
+    };
+
+    this.getViewport = function(me) {
+      var dom = (me && me.parentNode) ? me.parentNode : false;
+      while (dom && (dom.nodeType == 1) && !(/^BODY|HTML/.test(dom.nodeName))) {
+        var dd = $(dom);
+        if (/fixed|absolute/.test(dd.css("position"))) return dd;
+        var ov = dd.css('overflowY') || dd.css('overflowX') || dd.css('overflow') || '';
+        if ((/scroll|auto/.test(ov)) && (dom.clientHeight != dom.scrollHeight)) return dd;
+        if (dd.getNiceScroll().length > 0) return dd;
+        dom = (dom.parentNode) ? dom.parentNode : false;
+      }
+      return false; //(dom) ? $(dom) : false;
+    };
+
+    this.triggerScrollEnd = function() {
+      if (!self.onscrollend) return;
+
+      var px = self.getScrollLeft();
+      var py = self.getScrollTop();
+
+      var info = {
+        "type": "scrollend",
+        "current": {
+          "x": px,
+          "y": py
+        },
+        "end": {
+          "x": px,
+          "y": py
+        }
+      };
+      self.onscrollend.call(self, info);
+    }
+
+    function execScrollWheel(e, hr, chkscroll) {
+      var px, py;
+      
+      if (e.deltaMode == 0) { // PIXEL
+        px = -Math.floor(e.deltaX * (self.opt.mousescrollstep / (18 * 3)));
+        py = -Math.floor(e.deltaY * (self.opt.mousescrollstep / (18 * 3)));
+      } else if (e.deltaMode == 1) { // LINE
+        px = -Math.floor(e.deltaX * self.opt.mousescrollstep);
+        py = -Math.floor(e.deltaY * self.opt.mousescrollstep);
+      }
+
+      if (hr && self.opt.oneaxismousemode && (px == 0) && py) { // classic vertical-only mousewheel + browser with x/y support 
+        px = py;
+        py = 0;
+      
+        if (chkscroll) {
+          var hrend = (px < 0) ? (self.getScrollLeft() >= self.page.maxw) : (self.getScrollLeft() <= 0);
+          if (hrend) {  // preserve vertical scrolling
+            py = px;
+            px = 0;            
+          }
+        }
+        
+      }
+
+      if (px) {
+        if (self.scrollmom) {
+          self.scrollmom.stop()
+        }
+        self.lastdeltax += px;
+        self.debounced("mousewheelx", function() {
+          var dt = self.lastdeltax;
+          self.lastdeltax = 0;
+          if (!self.rail.drag) {
+            self.doScrollLeftBy(dt)
+          }
+        }, 15);
+      }
+      if (py) {
+        if (self.opt.nativeparentscrolling && chkscroll && !self.ispage && !self.zoomactive) {
+          if (py < 0) {
+            if (self.getScrollTop() >= self.page.maxh) return true;
+          } else {
+            if (self.getScrollTop() <= 0) return true;
+          }
+        }
+        if (self.scrollmom) {
+          self.scrollmom.stop()
+        }
+        self.lastdeltay += py;
+        self.debounced("mousewheely", function() {
+          var dt = self.lastdeltay;
+          self.lastdeltay = 0;
+          if (!self.rail.drag) {
+            self.doScrollBy(dt)
+          }
+        }, 15);
+      }
+
+      e.stopImmediatePropagation();
+      return e.preventDefault();
+    };
+
+    this.onmousewheel = function(e) {
+      if (self.wheelprevented) return;
+      if (self.railslocked) {
+        self.debounced("checkunlock", self.resize, 250);
+        return true;
+      }
+      if (self.rail.drag) return self.cancelEvent(e);
+
+      if (self.opt.oneaxismousemode == "auto" && e.deltaX != 0) self.opt.oneaxismousemode = false; // check two-axis mouse support (not very elegant)
+
+      if (self.opt.oneaxismousemode && e.deltaX == 0) {
+        if (!self.rail.scrollable) {
+          if (self.railh && self.railh.scrollable) {
+            return self.onmousewheelhr(e);
+          } else {
+            return true;
+          }
+        }
+      }
+
+      var nw = +(new Date());
+      var chk = false;
+      if (self.opt.preservenativescrolling && ((self.checkarea + 600) < nw)) {
+        self.nativescrollingarea = self.isScrollable(e);
+        chk = true;
+      }
+      self.checkarea = nw;
+      if (self.nativescrollingarea) return true; // this isn't my business
+      var ret = execScrollWheel(e, false, chk);
+      if (ret) self.checkarea = 0;
+      return ret;
+    };
+
+    this.onmousewheelhr = function(e) {
+      if (self.wheelprevented) return;
+      if (self.railslocked || !self.railh.scrollable) return true;
+      if (self.rail.drag) return self.cancelEvent(e);
+
+      var nw = +(new Date());
+      var chk = false;
+      if (self.opt.preservenativescrolling && ((self.checkarea + 600) < nw)) {
+        self.nativescrollingarea = self.isScrollable(e);
+        chk = true;
+      }
+      self.checkarea = nw;
+      if (self.nativescrollingarea) return true; // this isn't my business
+      if (self.railslocked) return self.cancelEvent(e);
+
+      return execScrollWheel(e, true, chk);
+    };
+
+    this.stop = function() {
+      self.cancelScroll();
+      if (self.scrollmon) self.scrollmon.stop();
+      self.cursorfreezed = false;
+      self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y));
+      self.noticeCursor();
+      return self;
+    };
+
+    this.getTransitionSpeed = function(dif) {
+      var sp = Math.round(self.opt.scrollspeed * 10);
+      var ex = Math.min(sp, Math.round((dif / 20) * self.opt.scrollspeed));
+      return (ex > 20) ? ex : 0;
+    };
+
+    if (!self.opt.smoothscroll) {
+      this.doScrollLeft = function(x, spd) { //direct
+        var y = self.getScrollTop();
+        self.doScrollPos(x, y, spd);
+      };
+      this.doScrollTop = function(y, spd) { //direct
+        var x = self.getScrollLeft();
+        self.doScrollPos(x, y, spd);
+      };
+      this.doScrollPos = function(x, y, spd) { //direct
+        var nx = (x > self.page.maxw) ? self.page.maxw : x;
+        if (nx < 0) nx = 0;
+        var ny = (y > self.page.maxh) ? self.page.maxh : y;
+        if (ny < 0) ny = 0;
+        self.synched('scroll', function() {
+          self.setScrollTop(ny);
+          self.setScrollLeft(nx);
+        });
+      };
+      this.cancelScroll = function() {}; // direct
+    } else if (self.ishwscroll && cap.hastransition && self.opt.usetransition && !!self.opt.smoothscroll) {
+      this.prepareTransition = function(dif, istime) {
+        var ex = (istime) ? ((dif > 20) ? dif : 0) : self.getTransitionSpeed(dif);
+        var trans = (ex) ? cap.prefixstyle + 'transform ' + ex + 'ms ease-out' : '';
+        if (!self.lasttransitionstyle || self.lasttransitionstyle != trans) {
+          self.lasttransitionstyle = trans;
+          self.doc.css(cap.transitionstyle, trans);
+        }
+        return ex;
+      };
+
+      this.doScrollLeft = function(x, spd) { //trans
+        var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop();
+        self.doScrollPos(x, y, spd);
+      };
+
+      this.doScrollTop = function(y, spd) { //trans
+        var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft();
+        self.doScrollPos(x, y, spd);
+      };
+
+      this.doScrollPos = function(x, y, spd) { //trans
+
+        var py = self.getScrollTop();
+        var px = self.getScrollLeft();
+
+        if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection      
+
+        if (self.opt.bouncescroll == false) {
+          if (y < 0) y = 0;
+          else if (y > self.page.maxh) y = self.page.maxh;
+          if (x < 0) x = 0;
+          else if (x > self.page.maxw) x = self.page.maxw;
+        }
+
+        if (self.scrollrunning && x == self.newscrollx && y == self.newscrolly) return false;
+
+        self.newscrolly = y;
+        self.newscrollx = x;
+
+        self.newscrollspeed = spd || false;
+
+        if (self.timer) return false;
+
+        self.timer = setTimeout(function() {
+
+          var top = self.getScrollTop();
+          var lft = self.getScrollLeft();
+
+          var dst = {};
+          dst.x = x - lft;
+          dst.y = y - top;
+          dst.px = lft;
+          dst.py = top;
+
+          var dd = Math.round(Math.sqrt(Math.pow(dst.x, 2) + Math.pow(dst.y, 2)));
+          var ms = (self.newscrollspeed && self.newscrollspeed > 1) ? self.newscrollspeed : self.getTransitionSpeed(dd);
+          if (self.newscrollspeed && self.newscrollspeed <= 1) ms *= self.newscrollspeed;
+
+          self.prepareTransition(ms, true);
+
+          if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm);
+
+          if (ms > 0) {
+
+            if (!self.scrollrunning && self.onscrollstart) {
+              var info = {
+                "type": "scrollstart",
+                "current": {
+                  "x": lft,
+                  "y": top
+                },
+                "request": {
+                  "x": x,
+                  "y": y
+                },
+                "end": {
+                  "x": self.newscrollx,
+                  "y": self.newscrolly
+                },
+                "speed": ms
+              };
+              self.onscrollstart.call(self, info);
+            }
+
+            if (cap.transitionend) {
+              if (!self.scrollendtrapped) {
+                self.scrollendtrapped = true;
+                self.bind(self.doc, cap.transitionend, self.onScrollTransitionEnd, false); //I have got to do something usefull!!
+              }
+            } else {
+              if (self.scrollendtrapped) clearTimeout(self.scrollendtrapped);
+              self.scrollendtrapped = setTimeout(self.onScrollTransitionEnd, ms); // simulate transitionend event
+            }
+
+            var py = top;
+            var px = lft;
+            self.timerscroll = {
+              bz: new BezierClass(py, self.newscrolly, ms, 0, 0, 0.58, 1),
+              bh: new BezierClass(px, self.newscrollx, ms, 0, 0, 0.58, 1)
+            };
+            if (!self.cursorfreezed) self.timerscroll.tm = setInterval(function() {
+              self.showCursor(self.getScrollTop(), self.getScrollLeft())
+            }, 60);
+
+          }
+
+          self.synched("doScroll-set", function() {
+            self.timer = 0;
+            if (self.scrollendtrapped) self.scrollrunning = true;
+            self.setScrollTop(self.newscrolly);
+            self.setScrollLeft(self.newscrollx);
+            if (!self.scrollendtrapped) self.onScrollTransitionEnd();
+          });
+
+
+        }, 50);
+
+      };
+
+      this.cancelScroll = function() {
+        if (!self.scrollendtrapped) return true;
+        var py = self.getScrollTop();
+        var px = self.getScrollLeft();
+        self.scrollrunning = false;
+        if (!cap.transitionend) clearTimeout(cap.transitionend);
+        self.scrollendtrapped = false;
+        self._unbind(self.doc[0], cap.transitionend, self.onScrollTransitionEnd);
+        self.prepareTransition(0);
+        self.setScrollTop(py); // fire event onscroll
+        if (self.railh) self.setScrollLeft(px);
+        if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm);
+        self.timerscroll = false;
+
+        self.cursorfreezed = false;
+
+        self.showCursor(py, px);
+        return self;
+      };
+      this.onScrollTransitionEnd = function() {
+        if (self.scrollendtrapped) self._unbind(self.doc[0], cap.transitionend, self.onScrollTransitionEnd);
+        self.scrollendtrapped = false;
+        self.prepareTransition(0);
+        if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm);
+        self.timerscroll = false;
+        var py = self.getScrollTop();
+        var px = self.getScrollLeft();
+        self.setScrollTop(py); // fire event onscroll        
+        if (self.railh) self.setScrollLeft(px); // fire event onscroll left
+
+        self.noticeCursor(false, py, px);
+
+        self.cursorfreezed = false;
+
+        if (py < 0) py = 0
+        else if (py > self.page.maxh) py = self.page.maxh;
+        if (px < 0) px = 0
+        else if (px > self.page.maxw) px = self.page.maxw;
+        if ((py != self.newscrolly) || (px != self.newscrollx)) return self.doScrollPos(px, py, self.opt.snapbackspeed);
+
+        if (self.onscrollend && self.scrollrunning) {
+          self.triggerScrollEnd();
+        }
+        self.scrollrunning = false;
+
+      };
+
+    } else {
+
+      this.doScrollLeft = function(x, spd) { //no-trans
+        var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop();
+        self.doScrollPos(x, y, spd);
+      };
+
+      this.doScrollTop = function(y, spd) { //no-trans
+        var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft();
+        self.doScrollPos(x, y, spd);
+      };
+
+      this.doScrollPos = function(x, y, spd) { //no-trans
+        var y = ((typeof y == "undefined") || (y === false)) ? self.getScrollTop(true) : y;
+
+        if ((self.timer) && (self.newscrolly == y) && (self.newscrollx == x)) return true;
+
+        if (self.timer) clearAnimationFrame(self.timer);
+        self.timer = 0;
+
+        var py = self.getScrollTop();
+        var px = self.getScrollLeft();
+
+        if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection
+
+        self.newscrolly = y;
+        self.newscrollx = x;
+
+        if (!self.bouncescroll || !self.rail.visibility) {
+          if (self.newscrolly < 0) {
+            self.newscrolly = 0;
+          } else if (self.newscrolly > self.page.maxh) {
+            self.newscrolly = self.page.maxh;
+          }
+        }
+        if (!self.bouncescroll || !self.railh.visibility) {
+          if (self.newscrollx < 0) {
+            self.newscrollx = 0;
+          } else if (self.newscrollx > self.page.maxw) {
+            self.newscrollx = self.page.maxw;
+          }
+        }
+
+        self.dst = {};
+        self.dst.x = x - px;
+        self.dst.y = y - py;
+        self.dst.px = px;
+        self.dst.py = py;
+
+        var dst = Math.round(Math.sqrt(Math.pow(self.dst.x, 2) + Math.pow(self.dst.y, 2)));
+
+        self.dst.ax = self.dst.x / dst;
+        self.dst.ay = self.dst.y / dst;
+
+        var pa = 0;
+        var pe = dst;
+
+        if (self.dst.x == 0) {
+          pa = py;
+          pe = y;
+          self.dst.ay = 1;
+          self.dst.py = 0;
+        } else if (self.dst.y == 0) {
+          pa = px;
+          pe = x;
+          self.dst.ax = 1;
+          self.dst.px = 0;
+        }
+
+        var ms = self.getTransitionSpeed(dst);
+        if (spd && spd <= 1) ms *= spd;
+        if (ms > 0) {
+          self.bzscroll = (self.bzscroll) ? self.bzscroll.update(pe, ms) : new BezierClass(pa, pe, ms, 0, 1, 0, 1);
+        } else {
+          self.bzscroll = false;
+        }
+
+        if (self.timer) return;
+
+        if ((py == self.page.maxh && y >= self.page.maxh) || (px == self.page.maxw && x >= self.page.maxw)) self.checkContentSize();
+
+        var sync = 1;
+
+        function scrolling() {
+          if (self.cancelAnimationFrame) return true;
+
+          self.scrollrunning = true;
+
+          sync = 1 - sync;
+          if (sync) return (self.timer = setAnimationFrame(scrolling) || 1);
+
+          var done = 0;
+          var sx, sy;
+
+          var sc = sy = self.getScrollTop();
+          if (self.dst.ay) {
+            sc = (self.bzscroll) ? self.dst.py + (self.bzscroll.getNow() * self.dst.ay) : self.newscrolly;
+            var dr = sc - sy;
+            if ((dr < 0 && sc < self.newscrolly) || (dr > 0 && sc > self.newscrolly)) sc = self.newscrolly;
+            self.setScrollTop(sc);
+            if (sc == self.newscrolly) done = 1;
+          } else {
+            done = 1;
+          }
+
+          var scx = sx = self.getScrollLeft();
+          if (self.dst.ax) {
+            scx = (self.bzscroll) ? self.dst.px + (self.bzscroll.getNow() * self.dst.ax) : self.newscrollx;
+            var dr = scx - sx;
+            if ((dr < 0 && scx < self.newscrollx) || (dr > 0 && scx > self.newscrollx)) scx = self.newscrollx;
+            self.setScrollLeft(scx);
+            if (scx == self.newscrollx) done += 1;
+          } else {
+            done += 1;
+          }
+
+          if (done == 2) {
+            self.timer = 0;
+            self.cursorfreezed = false;
+            self.bzscroll = false;
+            self.scrollrunning = false;
+            if (sc < 0) sc = 0;
+            else if (sc > self.page.maxh) sc = self.page.maxh;
+            if (scx < 0) scx = 0;
+            else if (scx > self.page.maxw) scx = self.page.maxw;
+            if ((scx != self.newscrollx) || (sc != self.newscrolly)) self.doScrollPos(scx, sc);
+            else {
+              if (self.onscrollend) {
+                self.triggerScrollEnd();
+              }
+            }
+          } else {
+            self.timer = setAnimationFrame(scrolling) || 1;
+          }
+        };
+        self.cancelAnimationFrame = false;
+        self.timer = 1;
+
+        if (self.onscrollstart && !self.scrollrunning) {
+          var info = {
+            "type": "scrollstart",
+            "current": {
+              "x": px,
+              "y": py
+            },
+            "request": {
+              "x": x,
+              "y": y
+            },
+            "end": {
+              "x": self.newscrollx,
+              "y": self.newscrolly
+            },
+            "speed": ms
+          };
+          self.onscrollstart.call(self, info);
+        }
+
+        scrolling();
+
+        if ((py == self.page.maxh && y >= py) || (px == self.page.maxw && x >= px)) self.checkContentSize();
+
+        self.noticeCursor();
+      };
+
+      this.cancelScroll = function() {
+        if (self.timer) clearAnimationFrame(self.timer);
+        self.timer = 0;
+        self.bzscroll = false;
+        self.scrollrunning = false;
+        return self;
+      };
+
+    }
+
+    this.doScrollBy = function(stp, relative) {
+      var ny = 0;
+      if (relative) {
+        ny = Math.floor((self.scroll.y - stp) * self.scrollratio.y)
+      } else {
+        var sy = (self.timer) ? self.newscrolly : self.getScrollTop(true);
+        ny = sy - stp;
+      }
+      if (self.bouncescroll) {
+        var haf = Math.round(self.view.h / 2);
+        if (ny < -haf) ny = -haf
+        else if (ny > (self.page.maxh + haf)) ny = (self.page.maxh + haf);
+      }
+      self.cursorfreezed = false;
+
+      var py = self.getScrollTop(true);
+      if (ny < 0 && py <= 0) return self.noticeCursor();
+      else if (ny > self.page.maxh && py >= self.page.maxh) {
+        self.checkContentSize();
+        return self.noticeCursor();
+      }
+
+      self.doScrollTop(ny);
+    };
+
+    this.doScrollLeftBy = function(stp, relative) {
+      var nx = 0;
+      if (relative) {
+        nx = Math.floor((self.scroll.x - stp) * self.scrollratio.x)
+      } else {
+        var sx = (self.timer) ? self.newscrollx : self.getScrollLeft(true);
+        nx = sx - stp;
+      }
+      if (self.bouncescroll) {
+        var haf = Math.round(self.view.w / 2);
+        if (nx < -haf) nx = -haf;
+        else if (nx > (self.page.maxw + haf)) nx = (self.page.maxw + haf);
+      }
+      self.cursorfreezed = false;
+
+      var px = self.getScrollLeft(true);
+      if (nx < 0 && px <= 0) return self.noticeCursor();
+      else if (nx > self.page.maxw && px >= self.page.maxw) return self.noticeCursor();
+
+      self.doScrollLeft(nx);
+    };
+
+    this.doScrollTo = function(pos, relative) {
+      var ny = (relative) ? Math.round(pos * self.scrollratio.y) : pos;
+      if (ny < 0) ny = 0;
+      else if (ny > self.page.maxh) ny = self.page.maxh;
+      self.cursorfreezed = false;
+      self.doScrollTop(pos);
+    };
+
+    this.checkContentSize = function() {
+      var pg = self.getContentSize();
+      if ((pg.h != self.page.h) || (pg.w != self.page.w)) self.resize(false, pg);
+    };
+
+    self.onscroll = function(e) {
+      if (self.rail.drag) return;
+      if (!self.cursorfreezed) {
+        self.synched('scroll', function() {
+          self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y));
+          if (self.railh) self.scroll.x = Math.round(self.getScrollLeft() * (1 / self.scrollratio.x));
+          self.noticeCursor();
+        });
+      }
+    };
+    self.bind(self.docscroll, "scroll", self.onscroll);
+
+    this.doZoomIn = function(e) {
+      if (self.zoomactive) return;
+      self.zoomactive = true;
+
+      self.zoomrestore = {
+        style: {}
+      };
+      var lst = ['position', 'top', 'left', 'zIndex', 'backgroundColor', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight'];
+      var win = self.win[0].style;
+      for (var a in lst) {
+        var pp = lst[a];
+        self.zoomrestore.style[pp] = (typeof win[pp] != "undefined") ? win[pp] : '';
+      }
+
+      self.zoomrestore.style.width = self.win.css('width');
+      self.zoomrestore.style.height = self.win.css('height');
+
+      self.zoomrestore.padding = {
+        w: self.win.outerWidth() - self.win.width(),
+        h: self.win.outerHeight() - self.win.height()
+      };
+
+      if (cap.isios4) {
+        self.zoomrestore.scrollTop = $(window).scrollTop();
+        $(window).scrollTop(0);
+      }
+
+      self.win.css({
+        "position": (cap.isios4) ? "absolute" : "fixed",
+        "top": 0,
+        "left": 0,
+        "z-index": globalmaxzindex + 100,
+        "margin": "0px"
+      });
+      var bkg = self.win.css("backgroundColor");
+      if (bkg == "" || /transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(bkg)) self.win.css("backgroundColor", "#fff");
+      self.rail.css({
+        "z-index": globalmaxzindex + 101
+      });
+      self.zoom.css({
+        "z-index": globalmaxzindex + 102
+      });
+      self.zoom.css('backgroundPosition', '0px -18px');
+      self.resizeZoom();
+
+      if (self.onzoomin) self.onzoomin.call(self);
+
+      return self.cancelEvent(e);
+    };
+
+    this.doZoomOut = function(e) {
+      if (!self.zoomactive) return;
+      self.zoomactive = false;
+
+      self.win.css("margin", "");
+      self.win.css(self.zoomrestore.style);
+
+      if (cap.isios4) {
+        $(window).scrollTop(self.zoomrestore.scrollTop);
+      }
+
+      self.rail.css({
+        "z-index": self.zindex
+      });
+      self.zoom.css({
+        "z-index": self.zindex
+      });
+      self.zoomrestore = false;
+      self.zoom.css('backgroundPosition', '0px 0px');
+      self.onResize();
+
+      if (self.onzoomout) self.onzoomout.call(self);
+
+      return self.cancelEvent(e);
+    };
+
+    this.doZoom = function(e) {
+      return (self.zoomactive) ? self.doZoomOut(e) : self.doZoomIn(e);
+    };
+
+    this.resizeZoom = function() {
+      if (!self.zoomactive) return;
+
+      var py = self.getScrollTop(); //preserve scrolling position
+      self.win.css({
+        width: $(window).width() - self.zoomrestore.padding.w + "px",
+        height: $(window).height() - self.zoomrestore.padding.h + "px"
+      });
+      self.onResize();
+
+      self.setScrollTop(Math.min(self.page.maxh, py));
+    };
+
+    this.init();
+
+    $.nicescroll.push(this);
+
+  };
+
+  // Inspired by the work of Kin Blas
+  // http://webpro.host.adobe.com/people/jblas/momentum/includes/jquery.momentum.0.7.js  
+
+
+  var ScrollMomentumClass2D = function(nc) {
+    var self = this;
+    this.nc = nc;
+
+    this.lastx = 0;
+    this.lasty = 0;
+    this.speedx = 0;
+    this.speedy = 0;
+    this.lasttime = 0;
+    this.steptime = 0;
+    this.snapx = false;
+    this.snapy = false;
+    this.demulx = 0;
+    this.demuly = 0;
+
+    this.lastscrollx = -1;
+    this.lastscrolly = -1;
+
+    this.chkx = 0;
+    this.chky = 0;
+
+    this.timer = 0;
+
+    this.time = function() {
+      return +new Date(); //beautifull hack
+    };
+
+    this.reset = function(px, py) {
+      self.stop();
+      var now = self.time();
+      self.steptime = 0;
+      self.lasttime = now;
+      self.speedx = 0;
+      self.speedy = 0;
+      self.lastx = px;
+      self.lasty = py;
+      self.lastscrollx = -1;
+      self.lastscrolly = -1;
+    };
+
+    this.update = function(px, py) {
+      var now = self.time();
+      self.steptime = now - self.lasttime;
+      self.lasttime = now;
+      var dy = py - self.lasty;
+      var dx = px - self.lastx;
+      var sy = self.nc.getScrollTop();
+      var sx = self.nc.getScrollLeft();
+      var newy = sy + dy;
+      var newx = sx + dx;
+      self.snapx = (newx < 0) || (newx > self.nc.page.maxw);
+      self.snapy = (newy < 0) || (newy > self.nc.page.maxh);
+      self.speedx = dx;
+      self.speedy = dy;
+      self.lastx = px;
+      self.lasty = py;
+    };
+
+    this.stop = function() {
+      self.nc.unsynched("domomentum2d");
+      if (self.timer) clearTimeout(self.timer);
+      self.timer = 0;
+      self.lastscrollx = -1;
+      self.lastscrolly = -1;
+    };
+
+    this.doSnapy = function(nx, ny) {
+      var snap = false;
+
+      if (ny < 0) {
+        ny = 0;
+        snap = true;
+      } else if (ny > self.nc.page.maxh) {
+        ny = self.nc.page.maxh;
+        snap = true;
+      }
+
+      if (nx < 0) {
+        nx = 0;
+        snap = true;
+      } else if (nx > self.nc.page.maxw) {
+        nx = self.nc.page.maxw;
+        snap = true;
+      }
+
+      (snap) ? self.nc.doScrollPos(nx, ny, self.nc.opt.snapbackspeed): self.nc.triggerScrollEnd();
+    };
+
+    this.doMomentum = function(gp) {
+      var t = self.time();
+      var l = (gp) ? t + gp : self.lasttime;
+
+      var sl = self.nc.getScrollLeft();
+      var st = self.nc.getScrollTop();
+
+      var pageh = self.nc.page.maxh;
+      var pagew = self.nc.page.maxw;
+
+      self.speedx = (pagew > 0) ? Math.min(60, self.speedx) : 0;
+      self.speedy = (pageh > 0) ? Math.min(60, self.speedy) : 0;
+
+      var chk = l && (t - l) <= 60;
+
+      if ((st < 0) || (st > pageh) || (sl < 0) || (sl > pagew)) chk = false;
+
+      var sy = (self.speedy && chk) ? self.speedy : false;
+      var sx = (self.speedx && chk) ? self.speedx : false;
+
+      if (sy || sx) {
+        var tm = Math.max(16, self.steptime); //timeout granularity
+
+        if (tm > 50) { // do smooth
+          var xm = tm / 50;
+          self.speedx *= xm;
+          self.speedy *= xm;
+          tm = 50;
+        }
+
+        self.demulxy = 0;
+
+        self.lastscrollx = self.nc.getScrollLeft();
+        self.chkx = self.lastscrollx;
+        self.lastscrolly = self.nc.getScrollTop();
+        self.chky = self.lastscrolly;
+
+        var nx = self.lastscrollx;
+        var ny = self.lastscrolly;
+
+        var onscroll = function() {
+          var df = ((self.time() - t) > 600) ? 0.04 : 0.02;
+
+          if (self.speedx) {
+            nx = Math.floor(self.lastscrollx - (self.speedx * (1 - self.demulxy)));
+            self.lastscrollx = nx;
+            if ((nx < 0) || (nx > pagew)) df = 0.10;
+          }
+
+          if (self.speedy) {
+            ny = Math.floor(self.lastscrolly - (self.speedy * (1 - self.demulxy)));
+            self.lastscrolly = ny;
+            if ((ny < 0) || (ny > pageh)) df = 0.10;
+          }
+
+          self.demulxy = Math.min(1, self.demulxy + df);
+
+          self.nc.synched("domomentum2d", function() {
+
+            if (self.speedx) {
+              var scx = self.nc.getScrollLeft();
+              if (scx != self.chkx) self.stop();
+              self.chkx = nx;
+              self.nc.setScrollLeft(nx);
+            }
+
+            if (self.speedy) {
+              var scy = self.nc.getScrollTop();
+              if (scy != self.chky) self.stop();
+              self.chky = ny;
+              self.nc.setScrollTop(ny);
+            }
+
+            if (!self.timer) {
+              self.nc.hideCursor();
+              self.doSnapy(nx, ny);
+            }
+
+          });
+
+          if (self.demulxy < 1) {
+            self.timer = setTimeout(onscroll, tm);
+          } else {
+            self.stop();
+            self.nc.hideCursor();
+            self.doSnapy(nx, ny);
+          }
+        };
+
+        onscroll();
+
+      } else {
+        self.doSnapy(self.nc.getScrollLeft(), self.nc.getScrollTop());
+      }
+
+    }
+
+  };
+
+
+  // override jQuery scrollTop
+
+  var _scrollTop = jQuery.fn.scrollTop; // preserve original function
+
+  jQuery.cssHooks["pageYOffset"] = {
+    get: function(elem, computed, extra) {
+      var nice = $.data(elem, '__nicescroll') || false;
+      return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(elem);
+    },
+    set: function(elem, value) {
+      var nice = $.data(elem, '__nicescroll') || false;
+      (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)): _scrollTop.call(elem, value);
+      return this;
+    }
+  };
+
+  /*  
+  $.fx.step["scrollTop"] = function(fx){    
+    $.cssHooks["scrollTop"].set( fx.elem, fx.now + fx.unit );
+  };
+*/
+
+  jQuery.fn.scrollTop = function(value) {
+    if (typeof value == "undefined") {
+      var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false;
+      return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(this);
+    } else {
+      return this.each(function() {
+        var nice = $.data(this, '__nicescroll') || false;
+        (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)): _scrollTop.call($(this), value);
+      });
+    }
+  };
+
+  // override jQuery scrollLeft
+
+  var _scrollLeft = jQuery.fn.scrollLeft; // preserve original function
+
+  $.cssHooks.pageXOffset = {
+    get: function(elem, computed, extra) {
+      var nice = $.data(elem, '__nicescroll') || false;
+      return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(elem);
+    },
+    set: function(elem, value) {
+      var nice = $.data(elem, '__nicescroll') || false;
+      (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)): _scrollLeft.call(elem, value);
+      return this;
+    }
+  };
+
+  /*  
+  $.fx.step["scrollLeft"] = function(fx){
+    $.cssHooks["scrollLeft"].set( fx.elem, fx.now + fx.unit );
+  };  
+*/
+
+  jQuery.fn.scrollLeft = function(value) {
+    if (typeof value == "undefined") {
+      var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false;
+      return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(this);
+    } else {
+      return this.each(function() {
+        var nice = $.data(this, '__nicescroll') || false;
+        (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)): _scrollLeft.call($(this), value);
+      });
+    }
+  };
+
+  var NiceScrollArray = function(doms) {
+    var self = this;
+    this.length = 0;
+    this.name = "nicescrollarray";
+
+    this.each = function(fn) {
+      for (var a = 0, i = 0; a < self.length; a++) fn.call(self[a], i++);
+      return self;
+    };
+
+    this.push = function(nice) {
+      self[self.length] = nice;
+      self.length++;
+    };
+
+    this.eq = function(idx) {
+      return self[idx];
+    };
+
+    if (doms) {
+      for (var a = 0; a < doms.length; a++) {
+        var nice = $.data(doms[a], '__nicescroll') || false;
+        if (nice) {
+          this[this.length] = nice;
+          this.length++;
+        }
+      };
+    }
+
+    return this;
+  };
+
+  function mplex(el, lst, fn) {
+    for (var a = 0; a < lst.length; a++) fn(el, lst[a]);
+  };
+  mplex(
+    NiceScrollArray.prototype, ['show', 'hide', 'toggle', 'onResize', 'resize', 'remove', 'stop', 'doScrollPos'],
+    function(e, n) {
+      e[n] = function() {
+        var args = arguments;
+        return this.each(function() {
+          this[n].apply(this, args);
+        });
+      };
+    }
+  );
+
+  jQuery.fn.getNiceScroll = function(index) {
+    if (typeof index == "undefined") {
+      return new NiceScrollArray(this);
+    } else {
+      var nice = this[index] && $.data(this[index], '__nicescroll') || false;
+      return nice;
+    }
+  };
+
+  jQuery.extend(jQuery.expr[':'], {
+    nicescroll: function(a) {
+      return ($.data(a, '__nicescroll')) ? true : false;
+    }
+  });
+
+  $.fn.niceScroll = function(wrapper, opt) {
+    if (typeof opt == "undefined") {
+      if ((typeof wrapper == "object") && !("jquery" in wrapper)) {
+        opt = wrapper;
+        wrapper = false;
+      }
+    }
+    opt = $.extend({},opt); // cloning
+    var ret = new NiceScrollArray();
+    if (typeof opt == "undefined") opt = {};
+
+    if (wrapper || false) {
+      opt.doc = $(wrapper);
+      opt.win = $(this);
+    }
+    var docundef = !("doc" in opt);
+    if (!docundef && !("win" in opt)) opt.win = $(this);
+
+    this.each(function() {
+      var nice = $(this).data('__nicescroll') || false;
+      if (!nice) {
+        opt.doc = (docundef) ? $(this) : opt.doc;
+        nice = new NiceScrollClass(opt, $(this));
+        $(this).data('__nicescroll', nice);
+      }
+      ret.push(nice);
+    });
+    return (ret.length == 1) ? ret[0] : ret;
+  };
+
+  window.NiceScroll = {
+    getjQuery: function() {
+      return jQuery
+    }
+  };
+
+  if (!$.nicescroll) {
+    $.nicescroll = new NiceScrollArray();
+    $.nicescroll.options = _globaloptions;
+  }
+
+}));
\ No newline at end of file
diff --git a/vendor/assets/javascripts/jquery.nicescroll.min.js b/vendor/assets/javascripts/jquery.nicescroll.min.js
deleted file mode 100644
index 5440b6a0da02e3a94a04ea4dd316546750d5efe8..0000000000000000000000000000000000000000
--- a/vendor/assets/javascripts/jquery.nicescroll.min.js
+++ /dev/null
@@ -1,118 +0,0 @@
-/* jquery.nicescroll 3.6.0 InuYaksa*2014 MIT http://nicescroll.areaaperta.com */(function(f){"function"===typeof define&&define.amd?define(["jquery"],f):f(jQuery)})(function(f){var y=!1,D=!1,N=0,O=2E3,x=0,H=["webkit","ms","moz","o"],s=window.requestAnimationFrame||!1,t=window.cancelAnimationFrame||!1;if(!s)for(var P in H){var E=H[P];s||(s=window[E+"RequestAnimationFrame"]);t||(t=window[E+"CancelAnimationFrame"]||window[E+"CancelRequestAnimationFrame"])}var v=window.MutationObserver||window.WebKitMutationObserver||!1,I={zindex:"auto",cursoropacitymin:0,cursoropacitymax:1,cursorcolor:"#424242",
-cursorwidth:"5px",cursorborder:"1px solid #fff",cursorborderradius:"5px",scrollspeed:60,mousescrollstep:24,touchbehavior:!1,hwacceleration:!0,usetransition:!0,boxzoom:!1,dblclickzoom:!0,gesturezoom:!0,grabcursorenabled:!0,autohidemode:!0,background:"",iframeautoresize:!0,cursorminheight:32,preservenativescrolling:!0,railoffset:!1,railhoffset:!1,bouncescroll:!0,spacebarenabled:!0,railpadding:{top:0,right:0,left:0,bottom:0},disableoutline:!0,horizrailenabled:!0,railalign:"right",railvalign:"bottom",
-enabletranslate3d:!0,enablemousewheel:!0,enablekeyboard:!0,smoothscroll:!0,sensitiverail:!0,enablemouselockapi:!0,cursorfixedheight:!1,directionlockdeadzone:6,hidecursordelay:400,nativeparentscrolling:!0,enablescrollonselection:!0,overflowx:!0,overflowy:!0,cursordragspeed:.3,rtlmode:"auto",cursordragontouch:!1,oneaxismousemode:"auto",scriptpath:function(){var f=document.getElementsByTagName("script"),f=f[f.length-1].src.split("?")[0];return 0<f.split("/").length?f.split("/").slice(0,-1).join("/")+
-"/":""}(),preventmultitouchscrolling:!0},F=!1,Q=function(){if(F)return F;var f=document.createElement("DIV"),c=f.style,h=navigator.userAgent,m=navigator.platform,d={haspointerlock:"pointerLockElement"in document||"webkitPointerLockElement"in document||"mozPointerLockElement"in document};d.isopera="opera"in window;d.isopera12=d.isopera&&"getUserMedia"in navigator;d.isoperamini="[object OperaMini]"===Object.prototype.toString.call(window.operamini);d.isie="all"in document&&"attachEvent"in f&&!d.isopera;
-d.isieold=d.isie&&!("msInterpolationMode"in c);d.isie7=d.isie&&!d.isieold&&(!("documentMode"in document)||7==document.documentMode);d.isie8=d.isie&&"documentMode"in document&&8==document.documentMode;d.isie9=d.isie&&"performance"in window&&9<=document.documentMode;d.isie10=d.isie&&"performance"in window&&10==document.documentMode;d.isie11="msRequestFullscreen"in f&&11<=document.documentMode;d.isie9mobile=/iemobile.9/i.test(h);d.isie9mobile&&(d.isie9=!1);d.isie7mobile=!d.isie9mobile&&d.isie7&&/iemobile/i.test(h);
-d.ismozilla="MozAppearance"in c;d.iswebkit="WebkitAppearance"in c;d.ischrome="chrome"in window;d.ischrome22=d.ischrome&&d.haspointerlock;d.ischrome26=d.ischrome&&"transition"in c;d.cantouch="ontouchstart"in document.documentElement||"ontouchstart"in window;d.hasmstouch=window.MSPointerEvent||!1;d.hasw3ctouch=window.PointerEvent||!1;d.ismac=/^mac$/i.test(m);d.isios=d.cantouch&&/iphone|ipad|ipod/i.test(m);d.isios4=d.isios&&!("seal"in Object);d.isios7=d.isios&&"webkitHidden"in document;d.isandroid=/android/i.test(h);
-d.haseventlistener="addEventListener"in f;d.trstyle=!1;d.hastransform=!1;d.hastranslate3d=!1;d.transitionstyle=!1;d.hastransition=!1;d.transitionend=!1;m=["transform","msTransform","webkitTransform","MozTransform","OTransform"];for(h=0;h<m.length;h++)if("undefined"!=typeof c[m[h]]){d.trstyle=m[h];break}d.hastransform=!!d.trstyle;d.hastransform&&(c[d.trstyle]="translate3d(1px,2px,3px)",d.hastranslate3d=/translate3d/.test(c[d.trstyle]));d.transitionstyle=!1;d.prefixstyle="";d.transitionend=!1;for(var m=
-"transition webkitTransition msTransition MozTransition OTransition OTransition KhtmlTransition".split(" "),n=" -webkit- -ms- -moz- -o- -o -khtml-".split(" "),p="transitionend webkitTransitionEnd msTransitionEnd transitionend otransitionend oTransitionEnd KhtmlTransitionEnd".split(" "),h=0;h<m.length;h++)if(m[h]in c){d.transitionstyle=m[h];d.prefixstyle=n[h];d.transitionend=p[h];break}d.ischrome26&&(d.prefixstyle=n[1]);d.hastransition=d.transitionstyle;a:{h=["-webkit-grab","-moz-grab","grab"];if(d.ischrome&&
-!d.ischrome22||d.isie)h=[];for(m=0;m<h.length;m++)if(n=h[m],c.cursor=n,c.cursor==n){c=n;break a}c="url(//mail.google.com/mail/images/2/openhand.cur),n-resize"}d.cursorgrabvalue=c;d.hasmousecapture="setCapture"in f;d.hasMutationObserver=!1!==v;return F=d},R=function(k,c){function h(){var b=a.doc.css(e.trstyle);return b&&"matrix"==b.substr(0,6)?b.replace(/^.*\((.*)\)$/g,"$1").replace(/px/g,"").split(/, +/):!1}function m(){var b=a.win;if("zIndex"in b)return b.zIndex();for(;0<b.length&&9!=b[0].nodeType;){var g=
-b.css("zIndex");if(!isNaN(g)&&0!=g)return parseInt(g);b=b.parent()}return!1}function d(b,g,q){g=b.css(g);b=parseFloat(g);return isNaN(b)?(b=w[g]||0,q=3==b?q?a.win.outerHeight()-a.win.innerHeight():a.win.outerWidth()-a.win.innerWidth():1,a.isie8&&b&&(b+=1),q?b:0):b}function n(b,g,q,c){a._bind(b,g,function(a){a=a?a:window.event;var c={original:a,target:a.target||a.srcElement,type:"wheel",deltaMode:"MozMousePixelScroll"==a.type?0:1,deltaX:0,deltaZ:0,preventDefault:function(){a.preventDefault?a.preventDefault():
-a.returnValue=!1;return!1},stopImmediatePropagation:function(){a.stopImmediatePropagation?a.stopImmediatePropagation():a.cancelBubble=!0}};"mousewheel"==g?(c.deltaY=-.025*a.wheelDelta,a.wheelDeltaX&&(c.deltaX=-.025*a.wheelDeltaX)):c.deltaY=a.detail;return q.call(b,c)},c)}function p(b,g,c){var d,e;0==b.deltaMode?(d=-Math.floor(a.opt.mousescrollstep/54*b.deltaX),e=-Math.floor(a.opt.mousescrollstep/54*b.deltaY)):1==b.deltaMode&&(d=-Math.floor(b.deltaX*a.opt.mousescrollstep),e=-Math.floor(b.deltaY*a.opt.mousescrollstep));
-g&&a.opt.oneaxismousemode&&0==d&&e&&(d=e,e=0,c&&(0>d?a.getScrollLeft()>=a.page.maxw:0>=a.getScrollLeft())&&(e=d,d=0));d&&(a.scrollmom&&a.scrollmom.stop(),a.lastdeltax+=d,a.debounced("mousewheelx",function(){var b=a.lastdeltax;a.lastdeltax=0;a.rail.drag||a.doScrollLeftBy(b)},15));if(e){if(a.opt.nativeparentscrolling&&c&&!a.ispage&&!a.zoomactive)if(0>e){if(a.getScrollTop()>=a.page.maxh)return!0}else if(0>=a.getScrollTop())return!0;a.scrollmom&&a.scrollmom.stop();a.lastdeltay+=e;a.debounced("mousewheely",
-function(){var b=a.lastdeltay;a.lastdeltay=0;a.rail.drag||a.doScrollBy(b)},15)}b.stopImmediatePropagation();return b.preventDefault()}var a=this;this.version="3.6.0";this.name="nicescroll";this.me=c;this.opt={doc:f("body"),win:!1};f.extend(this.opt,I);this.opt.snapbackspeed=80;if(k)for(var G in a.opt)"undefined"!=typeof k[G]&&(a.opt[G]=k[G]);this.iddoc=(this.doc=a.opt.doc)&&this.doc[0]?this.doc[0].id||"":"";this.ispage=/^BODY|HTML/.test(a.opt.win?a.opt.win[0].nodeName:this.doc[0].nodeName);this.haswrapper=
-!1!==a.opt.win;this.win=a.opt.win||(this.ispage?f(window):this.doc);this.docscroll=this.ispage&&!this.haswrapper?f(window):this.win;this.body=f("body");this.iframe=this.isfixed=this.viewport=!1;this.isiframe="IFRAME"==this.doc[0].nodeName&&"IFRAME"==this.win[0].nodeName;this.istextarea="TEXTAREA"==this.win[0].nodeName;this.forcescreen=!1;this.canshowonmouseevent="scroll"!=a.opt.autohidemode;this.page=this.view=this.onzoomout=this.onzoomin=this.onscrollcancel=this.onscrollend=this.onscrollstart=this.onclick=
-this.ongesturezoom=this.onkeypress=this.onmousewheel=this.onmousemove=this.onmouseup=this.onmousedown=!1;this.scroll={x:0,y:0};this.scrollratio={x:0,y:0};this.cursorheight=20;this.scrollvaluemax=0;this.isrtlmode="auto"==this.opt.rtlmode?"rtl"==(this.win[0]==window?this.body:this.win).css("direction"):!0===this.opt.rtlmode;this.observerbody=this.observerremover=this.observer=this.scrollmom=this.scrollrunning=!1;do this.id="ascrail"+O++;while(document.getElementById(this.id));this.hasmousefocus=this.hasfocus=
-this.zoomactive=this.zoom=this.selectiondrag=this.cursorfreezed=this.cursor=this.rail=!1;this.visibility=!0;this.hidden=this.locked=this.railslocked=!1;this.cursoractive=!0;this.wheelprevented=!1;this.overflowx=a.opt.overflowx;this.overflowy=a.opt.overflowy;this.nativescrollingarea=!1;this.checkarea=0;this.events=[];this.saved={};this.delaylist={};this.synclist={};this.lastdeltay=this.lastdeltax=0;this.detected=Q();var e=f.extend({},this.detected);this.ishwscroll=(this.canhwscroll=e.hastransform&&
-a.opt.hwacceleration)&&a.haswrapper;this.hasreversehr=this.isrtlmode&&!e.iswebkit;this.istouchcapable=!1;!e.cantouch||e.isios||e.isandroid||!e.iswebkit&&!e.ismozilla||(this.istouchcapable=!0,e.cantouch=!1);a.opt.enablemouselockapi||(e.hasmousecapture=!1,e.haspointerlock=!1);this.debounced=function(b,g,c){var d=a.delaylist[b];a.delaylist[b]=g;d||setTimeout(function(){var g=a.delaylist[b];a.delaylist[b]=!1;g.call(a)},c)};var r=!1;this.synched=function(b,g){a.synclist[b]=g;(function(){r||(s(function(){r=
-!1;for(var b in a.synclist){var g=a.synclist[b];g&&g.call(a);a.synclist[b]=!1}}),r=!0)})();return b};this.unsynched=function(b){a.synclist[b]&&(a.synclist[b]=!1)};this.css=function(b,g){for(var c in g)a.saved.css.push([b,c,b.css(c)]),b.css(c,g[c])};this.scrollTop=function(b){return"undefined"==typeof b?a.getScrollTop():a.setScrollTop(b)};this.scrollLeft=function(b){return"undefined"==typeof b?a.getScrollLeft():a.setScrollLeft(b)};var A=function(a,g,c,d,e,f,h){this.st=a;this.ed=g;this.spd=c;this.p1=
-d||0;this.p2=e||1;this.p3=f||0;this.p4=h||1;this.ts=(new Date).getTime();this.df=this.ed-this.st};A.prototype={B2:function(a){return 3*a*a*(1-a)},B3:function(a){return 3*a*(1-a)*(1-a)},B4:function(a){return(1-a)*(1-a)*(1-a)},getNow:function(){var a=1-((new Date).getTime()-this.ts)/this.spd,g=this.B2(a)+this.B3(a)+this.B4(a);return 0>a?this.ed:this.st+Math.round(this.df*g)},update:function(a,g){this.st=this.getNow();this.ed=a;this.spd=g;this.ts=(new Date).getTime();this.df=this.ed-this.st;return this}};
-if(this.ishwscroll){this.doc.translate={x:0,y:0,tx:"0px",ty:"0px"};e.hastranslate3d&&e.isios&&this.doc.css("-webkit-backface-visibility","hidden");this.getScrollTop=function(b){if(!b){if(b=h())return 16==b.length?-b[13]:-b[5];if(a.timerscroll&&a.timerscroll.bz)return a.timerscroll.bz.getNow()}return a.doc.translate.y};this.getScrollLeft=function(b){if(!b){if(b=h())return 16==b.length?-b[12]:-b[4];if(a.timerscroll&&a.timerscroll.bh)return a.timerscroll.bh.getNow()}return a.doc.translate.x};this.notifyScrollEvent=
-function(a){var g=document.createEvent("UIEvents");g.initUIEvent("scroll",!1,!0,window,1);g.niceevent=!0;a.dispatchEvent(g)};var K=this.isrtlmode?1:-1;e.hastranslate3d&&a.opt.enabletranslate3d?(this.setScrollTop=function(b,g){a.doc.translate.y=b;a.doc.translate.ty=-1*b+"px";a.doc.css(e.trstyle,"translate3d("+a.doc.translate.tx+","+a.doc.translate.ty+",0px)");g||a.notifyScrollEvent(a.win[0])},this.setScrollLeft=function(b,g){a.doc.translate.x=b;a.doc.translate.tx=b*K+"px";a.doc.css(e.trstyle,"translate3d("+
-a.doc.translate.tx+","+a.doc.translate.ty+",0px)");g||a.notifyScrollEvent(a.win[0])}):(this.setScrollTop=function(b,g){a.doc.translate.y=b;a.doc.translate.ty=-1*b+"px";a.doc.css(e.trstyle,"translate("+a.doc.translate.tx+","+a.doc.translate.ty+")");g||a.notifyScrollEvent(a.win[0])},this.setScrollLeft=function(b,g){a.doc.translate.x=b;a.doc.translate.tx=b*K+"px";a.doc.css(e.trstyle,"translate("+a.doc.translate.tx+","+a.doc.translate.ty+")");g||a.notifyScrollEvent(a.win[0])})}else this.getScrollTop=
-function(){return a.docscroll.scrollTop()},this.setScrollTop=function(b){return a.docscroll.scrollTop(b)},this.getScrollLeft=function(){return a.detected.ismozilla&&a.isrtlmode?Math.abs(a.docscroll.scrollLeft()):a.docscroll.scrollLeft()},this.setScrollLeft=function(b){return a.docscroll.scrollLeft(a.detected.ismozilla&&a.isrtlmode?-b:b)};this.getTarget=function(a){return a?a.target?a.target:a.srcElement?a.srcElement:!1:!1};this.hasParent=function(a,g){if(!a)return!1;for(var c=a.target||a.srcElement||
-a||!1;c&&c.id!=g;)c=c.parentNode||!1;return!1!==c};var w={thin:1,medium:3,thick:5};this.getDocumentScrollOffset=function(){return{top:window.pageYOffset||document.documentElement.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft}};this.getOffset=function(){if(a.isfixed){var b=a.win.offset(),g=a.getDocumentScrollOffset();b.top-=g.top;b.left-=g.left;return b}b=a.win.offset();if(!a.viewport)return b;g=a.viewport.offset();return{top:b.top-g.top,left:b.left-g.left}};this.updateScrollBar=
-function(b){if(a.ishwscroll)a.rail.css({height:a.win.innerHeight()-(a.opt.railpadding.top+a.opt.railpadding.bottom)}),a.railh&&a.railh.css({width:a.win.innerWidth()-(a.opt.railpadding.left+a.opt.railpadding.right)});else{var g=a.getOffset(),c=g.top,e=g.left-(a.opt.railpadding.left+a.opt.railpadding.right),c=c+d(a.win,"border-top-width",!0),e=e+(a.rail.align?a.win.outerWidth()-d(a.win,"border-right-width")-a.rail.width:d(a.win,"border-left-width")),f=a.opt.railoffset;f&&(f.top&&(c+=f.top),a.rail.align&&
-f.left&&(e+=f.left));a.railslocked||a.rail.css({top:c,left:e,height:(b?b.h:a.win.innerHeight())-(a.opt.railpadding.top+a.opt.railpadding.bottom)});a.zoom&&a.zoom.css({top:c+1,left:1==a.rail.align?e-20:e+a.rail.width+4});if(a.railh&&!a.railslocked){c=g.top;e=g.left;if(f=a.opt.railhoffset)f.top&&(c+=f.top),f.left&&(e+=f.left);b=a.railh.align?c+d(a.win,"border-top-width",!0)+a.win.innerHeight()-a.railh.height:c+d(a.win,"border-top-width",!0);e+=d(a.win,"border-left-width");a.railh.css({top:b-(a.opt.railpadding.top+
-a.opt.railpadding.bottom),left:e,width:a.railh.width})}}};this.doRailClick=function(b,g,c){var e;a.railslocked||(a.cancelEvent(b),g?(g=c?a.doScrollLeft:a.doScrollTop,e=c?(b.pageX-a.railh.offset().left-a.cursorwidth/2)*a.scrollratio.x:(b.pageY-a.rail.offset().top-a.cursorheight/2)*a.scrollratio.y,g(e)):(g=c?a.doScrollLeftBy:a.doScrollBy,e=c?a.scroll.x:a.scroll.y,b=c?b.pageX-a.railh.offset().left:b.pageY-a.rail.offset().top,c=c?a.view.w:a.view.h,g(e>=b?c:-c)))};a.hasanimationframe=s;a.hascancelanimationframe=
-t;a.hasanimationframe?a.hascancelanimationframe||(t=function(){a.cancelAnimationFrame=!0}):(s=function(a){return setTimeout(a,15-Math.floor(+new Date/1E3)%16)},t=clearInterval);this.init=function(){a.saved.css=[];if(e.isie7mobile||e.isoperamini)return!0;e.hasmstouch&&a.css(a.ispage?f("html"):a.win,{"-ms-touch-action":"none"});a.zindex="auto";a.zindex=a.ispage||"auto"!=a.opt.zindex?a.opt.zindex:m()||"auto";!a.ispage&&"auto"!=a.zindex&&a.zindex>x&&(x=a.zindex);a.isie&&0==a.zindex&&"auto"==a.opt.zindex&&
-(a.zindex="auto");if(!a.ispage||!e.cantouch&&!e.isieold&&!e.isie9mobile){var b=a.docscroll;a.ispage&&(b=a.haswrapper?a.win:a.doc);e.isie9mobile||a.css(b,{"overflow-y":"hidden"});a.ispage&&e.isie7&&("BODY"==a.doc[0].nodeName?a.css(f("html"),{"overflow-y":"hidden"}):"HTML"==a.doc[0].nodeName&&a.css(f("body"),{"overflow-y":"hidden"}));!e.isios||a.ispage||a.haswrapper||a.css(f("body"),{"-webkit-overflow-scrolling":"touch"});var g=f(document.createElement("div"));g.css({position:"relative",top:0,"float":"right",
-width:a.opt.cursorwidth,height:"0px","background-color":a.opt.cursorcolor,border:a.opt.cursorborder,"background-clip":"padding-box","-webkit-border-radius":a.opt.cursorborderradius,"-moz-border-radius":a.opt.cursorborderradius,"border-radius":a.opt.cursorborderradius});g.hborder=parseFloat(g.outerHeight()-g.innerHeight());g.addClass("nicescroll-cursors");a.cursor=g;var c=f(document.createElement("div"));c.attr("id",a.id);c.addClass("nicescroll-rails nicescroll-rails-vr");var d,h,k=["left","right",
-"top","bottom"],J;for(J in k)h=k[J],(d=a.opt.railpadding[h])?c.css("padding-"+h,d+"px"):a.opt.railpadding[h]=0;c.append(g);c.width=Math.max(parseFloat(a.opt.cursorwidth),g.outerWidth());c.css({width:c.width+"px",zIndex:a.zindex,background:a.opt.background,cursor:"default"});c.visibility=!0;c.scrollable=!0;c.align="left"==a.opt.railalign?0:1;a.rail=c;g=a.rail.drag=!1;!a.opt.boxzoom||a.ispage||e.isieold||(g=document.createElement("div"),a.bind(g,"click",a.doZoom),a.bind(g,"mouseenter",function(){a.zoom.css("opacity",
-a.opt.cursoropacitymax)}),a.bind(g,"mouseleave",function(){a.zoom.css("opacity",a.opt.cursoropacitymin)}),a.zoom=f(g),a.zoom.css({cursor:"pointer","z-index":a.zindex,backgroundImage:"url("+a.opt.scriptpath+"zoomico.png)",height:18,width:18,backgroundPosition:"0px 0px"}),a.opt.dblclickzoom&&a.bind(a.win,"dblclick",a.doZoom),e.cantouch&&a.opt.gesturezoom&&(a.ongesturezoom=function(b){1.5<b.scale&&a.doZoomIn(b);.8>b.scale&&a.doZoomOut(b);return a.cancelEvent(b)},a.bind(a.win,"gestureend",a.ongesturezoom)));
-a.railh=!1;var l;a.opt.horizrailenabled&&(a.css(b,{"overflow-x":"hidden"}),g=f(document.createElement("div")),g.css({position:"absolute",top:0,height:a.opt.cursorwidth,width:"0px","background-color":a.opt.cursorcolor,border:a.opt.cursorborder,"background-clip":"padding-box","-webkit-border-radius":a.opt.cursorborderradius,"-moz-border-radius":a.opt.cursorborderradius,"border-radius":a.opt.cursorborderradius}),e.isieold&&g.css({overflow:"hidden"}),g.wborder=parseFloat(g.outerWidth()-g.innerWidth()),
-g.addClass("nicescroll-cursors"),a.cursorh=g,l=f(document.createElement("div")),l.attr("id",a.id+"-hr"),l.addClass("nicescroll-rails nicescroll-rails-hr"),l.height=Math.max(parseFloat(a.opt.cursorwidth),g.outerHeight()),l.css({height:l.height+"px",zIndex:a.zindex,background:a.opt.background}),l.append(g),l.visibility=!0,l.scrollable=!0,l.align="top"==a.opt.railvalign?0:1,a.railh=l,a.railh.drag=!1);a.ispage?(c.css({position:"fixed",top:"0px",height:"100%"}),c.align?c.css({right:"0px"}):c.css({left:"0px"}),
-a.body.append(c),a.railh&&(l.css({position:"fixed",left:"0px",width:"100%"}),l.align?l.css({bottom:"0px"}):l.css({top:"0px"}),a.body.append(l))):(a.ishwscroll?("static"==a.win.css("position")&&a.css(a.win,{position:"relative"}),b="HTML"==a.win[0].nodeName?a.body:a.win,f(b).scrollTop(0).scrollLeft(0),a.zoom&&(a.zoom.css({position:"absolute",top:1,right:0,"margin-right":c.width+4}),b.append(a.zoom)),c.css({position:"absolute",top:0}),c.align?c.css({right:0}):c.css({left:0}),b.append(c),l&&(l.css({position:"absolute",
-left:0,bottom:0}),l.align?l.css({bottom:0}):l.css({top:0}),b.append(l))):(a.isfixed="fixed"==a.win.css("position"),b=a.isfixed?"fixed":"absolute",a.isfixed||(a.viewport=a.getViewport(a.win[0])),a.viewport&&(a.body=a.viewport,0==/fixed|absolute/.test(a.viewport.css("position"))&&a.css(a.viewport,{position:"relative"})),c.css({position:b}),a.zoom&&a.zoom.css({position:b}),a.updateScrollBar(),a.body.append(c),a.zoom&&a.body.append(a.zoom),a.railh&&(l.css({position:b}),a.body.append(l))),e.isios&&a.css(a.win,
-{"-webkit-tap-highlight-color":"rgba(0,0,0,0)","-webkit-touch-callout":"none"}),e.isie&&a.opt.disableoutline&&a.win.attr("hideFocus","true"),e.iswebkit&&a.opt.disableoutline&&a.win.css({outline:"none"}));!1===a.opt.autohidemode?(a.autohidedom=!1,a.rail.css({opacity:a.opt.cursoropacitymax}),a.railh&&a.railh.css({opacity:a.opt.cursoropacitymax})):!0===a.opt.autohidemode||"leave"===a.opt.autohidemode?(a.autohidedom=f().add(a.rail),e.isie8&&(a.autohidedom=a.autohidedom.add(a.cursor)),a.railh&&(a.autohidedom=
-a.autohidedom.add(a.railh)),a.railh&&e.isie8&&(a.autohidedom=a.autohidedom.add(a.cursorh))):"scroll"==a.opt.autohidemode?(a.autohidedom=f().add(a.rail),a.railh&&(a.autohidedom=a.autohidedom.add(a.railh))):"cursor"==a.opt.autohidemode?(a.autohidedom=f().add(a.cursor),a.railh&&(a.autohidedom=a.autohidedom.add(a.cursorh))):"hidden"==a.opt.autohidemode&&(a.autohidedom=!1,a.hide(),a.railslocked=!1);if(e.isie9mobile)a.scrollmom=new L(a),a.onmangotouch=function(){var b=a.getScrollTop(),c=a.getScrollLeft();
-if(b==a.scrollmom.lastscrolly&&c==a.scrollmom.lastscrollx)return!0;var g=b-a.mangotouch.sy,e=c-a.mangotouch.sx;if(0!=Math.round(Math.sqrt(Math.pow(e,2)+Math.pow(g,2)))){var d=0>g?-1:1,f=0>e?-1:1,q=+new Date;a.mangotouch.lazy&&clearTimeout(a.mangotouch.lazy);80<q-a.mangotouch.tm||a.mangotouch.dry!=d||a.mangotouch.drx!=f?(a.scrollmom.stop(),a.scrollmom.reset(c,b),a.mangotouch.sy=b,a.mangotouch.ly=b,a.mangotouch.sx=c,a.mangotouch.lx=c,a.mangotouch.dry=d,a.mangotouch.drx=f,a.mangotouch.tm=q):(a.scrollmom.stop(),
-a.scrollmom.update(a.mangotouch.sx-e,a.mangotouch.sy-g),a.mangotouch.tm=q,g=Math.max(Math.abs(a.mangotouch.ly-b),Math.abs(a.mangotouch.lx-c)),a.mangotouch.ly=b,a.mangotouch.lx=c,2<g&&(a.mangotouch.lazy=setTimeout(function(){a.mangotouch.lazy=!1;a.mangotouch.dry=0;a.mangotouch.drx=0;a.mangotouch.tm=0;a.scrollmom.doMomentum(30)},100)))}},c=a.getScrollTop(),l=a.getScrollLeft(),a.mangotouch={sy:c,ly:c,dry:0,sx:l,lx:l,drx:0,lazy:!1,tm:0},a.bind(a.docscroll,"scroll",a.onmangotouch);else{if(e.cantouch||
-a.istouchcapable||a.opt.touchbehavior||e.hasmstouch){a.scrollmom=new L(a);a.ontouchstart=function(b){if(b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;a.hasmoving=!1;if(!a.railslocked){var c;if(e.hasmstouch)for(c=b.target?b.target:!1;c;){var g=f(c).getNiceScroll();if(0<g.length&&g[0].me==a.me)break;if(0<g.length)return!1;if("DIV"==c.nodeName&&c.id==a.id)break;c=c.parentNode?c.parentNode:!1}a.cancelScroll();if((c=a.getTarget(b))&&/INPUT/i.test(c.nodeName)&&/range/i.test(c.type))return a.stopPropagation(b);
-!("clientX"in b)&&"changedTouches"in b&&(b.clientX=b.changedTouches[0].clientX,b.clientY=b.changedTouches[0].clientY);a.forcescreen&&(g=b,b={original:b.original?b.original:b},b.clientX=g.screenX,b.clientY=g.screenY);a.rail.drag={x:b.clientX,y:b.clientY,sx:a.scroll.x,sy:a.scroll.y,st:a.getScrollTop(),sl:a.getScrollLeft(),pt:2,dl:!1};if(a.ispage||!a.opt.directionlockdeadzone)a.rail.drag.dl="f";else{var g=f(window).width(),d=f(window).height(),q=Math.max(document.body.scrollWidth,document.documentElement.scrollWidth),
-h=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight),d=Math.max(0,h-d),g=Math.max(0,q-g);a.rail.drag.ck=!a.rail.scrollable&&a.railh.scrollable?0<d?"v":!1:a.rail.scrollable&&!a.railh.scrollable?0<g?"h":!1:!1;a.rail.drag.ck||(a.rail.drag.dl="f")}a.opt.touchbehavior&&a.isiframe&&e.isie&&(g=a.win.position(),a.rail.drag.x+=g.left,a.rail.drag.y+=g.top);a.hasmoving=!1;a.lastmouseup=!1;a.scrollmom.reset(b.clientX,b.clientY);if(!e.cantouch&&!this.istouchcapable&&!b.pointerType){if(!c||
-!/INPUT|SELECT|TEXTAREA/i.test(c.nodeName))return!a.ispage&&e.hasmousecapture&&c.setCapture(),a.opt.touchbehavior?(c.onclick&&!c._onclick&&(c._onclick=c.onclick,c.onclick=function(b){if(a.hasmoving)return!1;c._onclick.call(this,b)}),a.cancelEvent(b)):a.stopPropagation(b);/SUBMIT|CANCEL|BUTTON/i.test(f(c).attr("type"))&&(pc={tg:c,click:!1},a.preventclick=pc)}}};a.ontouchend=function(b){if(!a.rail.drag)return!0;if(2==a.rail.drag.pt){if(b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;
-a.scrollmom.doMomentum();a.rail.drag=!1;if(a.hasmoving&&(a.lastmouseup=!0,a.hideCursor(),e.hasmousecapture&&document.releaseCapture(),!e.cantouch))return a.cancelEvent(b)}else if(1==a.rail.drag.pt)return a.onmouseup(b)};var n=a.opt.touchbehavior&&a.isiframe&&!e.hasmousecapture;a.ontouchmove=function(b,c){if(!a.rail.drag||b.targetTouches&&a.opt.preventmultitouchscrolling&&1<b.targetTouches.length||b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;if(2==a.rail.drag.pt){if(e.cantouch&&
-e.isios&&"undefined"==typeof b.original)return!0;a.hasmoving=!0;a.preventclick&&!a.preventclick.click&&(a.preventclick.click=a.preventclick.tg.onclick||!1,a.preventclick.tg.onclick=a.onpreventclick);b=f.extend({original:b},b);"changedTouches"in b&&(b.clientX=b.changedTouches[0].clientX,b.clientY=b.changedTouches[0].clientY);if(a.forcescreen){var g=b;b={original:b.original?b.original:b};b.clientX=g.screenX;b.clientY=g.screenY}var d,g=d=0;n&&!c&&(d=a.win.position(),g=-d.left,d=-d.top);var q=b.clientY+
-d;d=q-a.rail.drag.y;var h=b.clientX+g,u=h-a.rail.drag.x,k=a.rail.drag.st-d;a.ishwscroll&&a.opt.bouncescroll?0>k?k=Math.round(k/2):k>a.page.maxh&&(k=a.page.maxh+Math.round((k-a.page.maxh)/2)):(0>k&&(q=k=0),k>a.page.maxh&&(k=a.page.maxh,q=0));var l;a.railh&&a.railh.scrollable&&(l=a.isrtlmode?u-a.rail.drag.sl:a.rail.drag.sl-u,a.ishwscroll&&a.opt.bouncescroll?0>l?l=Math.round(l/2):l>a.page.maxw&&(l=a.page.maxw+Math.round((l-a.page.maxw)/2)):(0>l&&(h=l=0),l>a.page.maxw&&(l=a.page.maxw,h=0)));g=!1;if(a.rail.drag.dl)g=
-!0,"v"==a.rail.drag.dl?l=a.rail.drag.sl:"h"==a.rail.drag.dl&&(k=a.rail.drag.st);else{d=Math.abs(d);var u=Math.abs(u),z=a.opt.directionlockdeadzone;if("v"==a.rail.drag.ck){if(d>z&&u<=.3*d)return a.rail.drag=!1,!0;u>z&&(a.rail.drag.dl="f",f("body").scrollTop(f("body").scrollTop()))}else if("h"==a.rail.drag.ck){if(u>z&&d<=.3*u)return a.rail.drag=!1,!0;d>z&&(a.rail.drag.dl="f",f("body").scrollLeft(f("body").scrollLeft()))}}a.synched("touchmove",function(){a.rail.drag&&2==a.rail.drag.pt&&(a.prepareTransition&&
-a.prepareTransition(0),a.rail.scrollable&&a.setScrollTop(k),a.scrollmom.update(h,q),a.railh&&a.railh.scrollable?(a.setScrollLeft(l),a.showCursor(k,l)):a.showCursor(k),e.isie10&&document.selection.clear())});e.ischrome&&a.istouchcapable&&(g=!1);if(g)return a.cancelEvent(b)}else if(1==a.rail.drag.pt)return a.onmousemove(b)}}a.onmousedown=function(b,c){if(!a.rail.drag||1==a.rail.drag.pt){if(a.railslocked)return a.cancelEvent(b);a.cancelScroll();a.rail.drag={x:b.clientX,y:b.clientY,sx:a.scroll.x,sy:a.scroll.y,
-pt:1,hr:!!c};var g=a.getTarget(b);!a.ispage&&e.hasmousecapture&&g.setCapture();a.isiframe&&!e.hasmousecapture&&(a.saved.csspointerevents=a.doc.css("pointer-events"),a.css(a.doc,{"pointer-events":"none"}));a.hasmoving=!1;return a.cancelEvent(b)}};a.onmouseup=function(b){if(a.rail.drag){if(1!=a.rail.drag.pt)return!0;e.hasmousecapture&&document.releaseCapture();a.isiframe&&!e.hasmousecapture&&a.doc.css("pointer-events",a.saved.csspointerevents);a.rail.drag=!1;a.hasmoving&&a.triggerScrollEnd();return a.cancelEvent(b)}};
-a.onmousemove=function(b){if(a.rail.drag&&1==a.rail.drag.pt){if(e.ischrome&&0==b.which)return a.onmouseup(b);a.cursorfreezed=!0;a.hasmoving=!0;if(a.rail.drag.hr){a.scroll.x=a.rail.drag.sx+(b.clientX-a.rail.drag.x);0>a.scroll.x&&(a.scroll.x=0);var c=a.scrollvaluemaxw;a.scroll.x>c&&(a.scroll.x=c)}else a.scroll.y=a.rail.drag.sy+(b.clientY-a.rail.drag.y),0>a.scroll.y&&(a.scroll.y=0),c=a.scrollvaluemax,a.scroll.y>c&&(a.scroll.y=c);a.synched("mousemove",function(){a.rail.drag&&1==a.rail.drag.pt&&(a.showCursor(),
-a.rail.drag.hr?a.hasreversehr?a.doScrollLeft(a.scrollvaluemaxw-Math.round(a.scroll.x*a.scrollratio.x),a.opt.cursordragspeed):a.doScrollLeft(Math.round(a.scroll.x*a.scrollratio.x),a.opt.cursordragspeed):a.doScrollTop(Math.round(a.scroll.y*a.scrollratio.y),a.opt.cursordragspeed))});return a.cancelEvent(b)}};if(e.cantouch||a.opt.touchbehavior)a.onpreventclick=function(b){if(a.preventclick)return a.preventclick.tg.onclick=a.preventclick.click,a.preventclick=!1,a.cancelEvent(b)},a.bind(a.win,"mousedown",
-a.ontouchstart),a.onclick=e.isios?!1:function(b){return a.lastmouseup?(a.lastmouseup=!1,a.cancelEvent(b)):!0},a.opt.grabcursorenabled&&e.cursorgrabvalue&&(a.css(a.ispage?a.doc:a.win,{cursor:e.cursorgrabvalue}),a.css(a.rail,{cursor:e.cursorgrabvalue}));else{var p=function(b){if(a.selectiondrag){if(b){var c=a.win.outerHeight();b=b.pageY-a.selectiondrag.top;0<b&&b<c&&(b=0);b>=c&&(b-=c);a.selectiondrag.df=b}0!=a.selectiondrag.df&&(a.doScrollBy(2*-Math.floor(a.selectiondrag.df/6)),a.debounced("doselectionscroll",
-function(){p()},50))}};a.hasTextSelected="getSelection"in document?function(){return 0<document.getSelection().rangeCount}:"selection"in document?function(){return"None"!=document.selection.type}:function(){return!1};a.onselectionstart=function(b){a.ispage||(a.selectiondrag=a.win.offset())};a.onselectionend=function(b){a.selectiondrag=!1};a.onselectiondrag=function(b){a.selectiondrag&&a.hasTextSelected()&&a.debounced("selectionscroll",function(){p(b)},250)}}e.hasw3ctouch?(a.css(a.rail,{"touch-action":"none"}),
-a.css(a.cursor,{"touch-action":"none"}),a.bind(a.win,"pointerdown",a.ontouchstart),a.bind(document,"pointerup",a.ontouchend),a.bind(document,"pointermove",a.ontouchmove)):e.hasmstouch?(a.css(a.rail,{"-ms-touch-action":"none"}),a.css(a.cursor,{"-ms-touch-action":"none"}),a.bind(a.win,"MSPointerDown",a.ontouchstart),a.bind(document,"MSPointerUp",a.ontouchend),a.bind(document,"MSPointerMove",a.ontouchmove),a.bind(a.cursor,"MSGestureHold",function(a){a.preventDefault()}),a.bind(a.cursor,"contextmenu",
-function(a){a.preventDefault()})):this.istouchcapable&&(a.bind(a.win,"touchstart",a.ontouchstart),a.bind(document,"touchend",a.ontouchend),a.bind(document,"touchcancel",a.ontouchend),a.bind(document,"touchmove",a.ontouchmove));if(a.opt.cursordragontouch||!e.cantouch&&!a.opt.touchbehavior)a.rail.css({cursor:"default"}),a.railh&&a.railh.css({cursor:"default"}),a.jqbind(a.rail,"mouseenter",function(){if(!a.ispage&&!a.win.is(":visible"))return!1;a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),
-a.jqbind(a.rail,"mouseleave",function(){a.rail.active=!1;a.rail.drag||a.hideCursor()}),a.opt.sensitiverail&&(a.bind(a.rail,"click",function(b){a.doRailClick(b,!1,!1)}),a.bind(a.rail,"dblclick",function(b){a.doRailClick(b,!0,!1)}),a.bind(a.cursor,"click",function(b){a.cancelEvent(b)}),a.bind(a.cursor,"dblclick",function(b){a.cancelEvent(b)})),a.railh&&(a.jqbind(a.railh,"mouseenter",function(){if(!a.ispage&&!a.win.is(":visible"))return!1;a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),a.jqbind(a.railh,
-"mouseleave",function(){a.rail.active=!1;a.rail.drag||a.hideCursor()}),a.opt.sensitiverail&&(a.bind(a.railh,"click",function(b){a.doRailClick(b,!1,!0)}),a.bind(a.railh,"dblclick",function(b){a.doRailClick(b,!0,!0)}),a.bind(a.cursorh,"click",function(b){a.cancelEvent(b)}),a.bind(a.cursorh,"dblclick",function(b){a.cancelEvent(b)})));e.cantouch||a.opt.touchbehavior?(a.bind(e.hasmousecapture?a.win:document,"mouseup",a.ontouchend),a.bind(document,"mousemove",a.ontouchmove),a.onclick&&a.bind(document,"click",
-a.onclick),a.opt.cursordragontouch&&(a.bind(a.cursor,"mousedown",a.onmousedown),a.bind(a.cursor,"mouseup",a.onmouseup),a.cursorh&&a.bind(a.cursorh,"mousedown",function(b){a.onmousedown(b,!0)}),a.cursorh&&a.bind(a.cursorh,"mouseup",a.onmouseup))):(a.bind(e.hasmousecapture?a.win:document,"mouseup",a.onmouseup),a.bind(document,"mousemove",a.onmousemove),a.onclick&&a.bind(document,"click",a.onclick),a.bind(a.cursor,"mousedown",a.onmousedown),a.bind(a.cursor,"mouseup",a.onmouseup),a.railh&&(a.bind(a.cursorh,
-"mousedown",function(b){a.onmousedown(b,!0)}),a.bind(a.cursorh,"mouseup",a.onmouseup)),!a.ispage&&a.opt.enablescrollonselection&&(a.bind(a.win[0],"mousedown",a.onselectionstart),a.bind(document,"mouseup",a.onselectionend),a.bind(a.cursor,"mouseup",a.onselectionend),a.cursorh&&a.bind(a.cursorh,"mouseup",a.onselectionend),a.bind(document,"mousemove",a.onselectiondrag)),a.zoom&&(a.jqbind(a.zoom,"mouseenter",function(){a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),a.jqbind(a.zoom,"mouseleave",
-function(){a.rail.active=!1;a.rail.drag||a.hideCursor()})));a.opt.enablemousewheel&&(a.isiframe||a.bind(e.isie&&a.ispage?document:a.win,"mousewheel",a.onmousewheel),a.bind(a.rail,"mousewheel",a.onmousewheel),a.railh&&a.bind(a.railh,"mousewheel",a.onmousewheelhr));a.ispage||e.cantouch||/HTML|^BODY/.test(a.win[0].nodeName)||(a.win.attr("tabindex")||a.win.attr({tabindex:N++}),a.jqbind(a.win,"focus",function(b){y=a.getTarget(b).id||!0;a.hasfocus=!0;a.canshowonmouseevent&&a.noticeCursor()}),a.jqbind(a.win,
-"blur",function(b){y=!1;a.hasfocus=!1}),a.jqbind(a.win,"mouseenter",function(b){D=a.getTarget(b).id||!0;a.hasmousefocus=!0;a.canshowonmouseevent&&a.noticeCursor()}),a.jqbind(a.win,"mouseleave",function(){D=!1;a.hasmousefocus=!1;a.rail.drag||a.hideCursor()}))}a.onkeypress=function(b){if(a.railslocked&&0==a.page.maxh)return!0;b=b?b:window.e;var c=a.getTarget(b);if(c&&/INPUT|TEXTAREA|SELECT|OPTION/.test(c.nodeName)&&(!c.getAttribute("type")&&!c.type||!/submit|button|cancel/i.tp)||f(c).attr("contenteditable"))return!0;
-if(a.hasfocus||a.hasmousefocus&&!y||a.ispage&&!y&&!D){c=b.keyCode;if(a.railslocked&&27!=c)return a.cancelEvent(b);var g=b.ctrlKey||!1,d=b.shiftKey||!1,e=!1;switch(c){case 38:case 63233:a.doScrollBy(72);e=!0;break;case 40:case 63235:a.doScrollBy(-72);e=!0;break;case 37:case 63232:a.railh&&(g?a.doScrollLeft(0):a.doScrollLeftBy(72),e=!0);break;case 39:case 63234:a.railh&&(g?a.doScrollLeft(a.page.maxw):a.doScrollLeftBy(-72),e=!0);break;case 33:case 63276:a.doScrollBy(a.view.h);e=!0;break;case 34:case 63277:a.doScrollBy(-a.view.h);
-e=!0;break;case 36:case 63273:a.railh&&g?a.doScrollPos(0,0):a.doScrollTo(0);e=!0;break;case 35:case 63275:a.railh&&g?a.doScrollPos(a.page.maxw,a.page.maxh):a.doScrollTo(a.page.maxh);e=!0;break;case 32:a.opt.spacebarenabled&&(d?a.doScrollBy(a.view.h):a.doScrollBy(-a.view.h),e=!0);break;case 27:a.zoomactive&&(a.doZoom(),e=!0)}if(e)return a.cancelEvent(b)}};a.opt.enablekeyboard&&a.bind(document,e.isopera&&!e.isopera12?"keypress":"keydown",a.onkeypress);a.bind(document,"keydown",function(b){b.ctrlKey&&
-(a.wheelprevented=!0)});a.bind(document,"keyup",function(b){b.ctrlKey||(a.wheelprevented=!1)});a.bind(window,"blur",function(b){a.wheelprevented=!1});a.bind(window,"resize",a.lazyResize);a.bind(window,"orientationchange",a.lazyResize);a.bind(window,"load",a.lazyResize);if(e.ischrome&&!a.ispage&&!a.haswrapper){var r=a.win.attr("style"),c=parseFloat(a.win.css("width"))+1;a.win.css("width",c);a.synched("chromefix",function(){a.win.attr("style",r)})}a.onAttributeChange=function(b){a.lazyResize(a.isieold?
-250:30)};!1!==v&&(a.observerbody=new v(function(b){b.forEach(function(b){if("attributes"==b.type)return f("body").hasClass("modal-open")?a.hide():a.show()});if(document.body.scrollHeight!=a.page.maxh)return a.lazyResize(30)}),a.observerbody.observe(document.body,{childList:!0,subtree:!0,characterData:!1,attributes:!0,attributeFilter:["class"]}));a.ispage||a.haswrapper||(!1!==v?(a.observer=new v(function(b){b.forEach(a.onAttributeChange)}),a.observer.observe(a.win[0],{childList:!0,characterData:!1,
-attributes:!0,subtree:!1}),a.observerremover=new v(function(b){b.forEach(function(b){if(0<b.removedNodes.length)for(var c in b.removedNodes)if(a&&b.removedNodes[c]==a.win[0])return a.remove()})}),a.observerremover.observe(a.win[0].parentNode,{childList:!0,characterData:!1,attributes:!1,subtree:!1})):(a.bind(a.win,e.isie&&!e.isie9?"propertychange":"DOMAttrModified",a.onAttributeChange),e.isie9&&a.win[0].attachEvent("onpropertychange",a.onAttributeChange),a.bind(a.win,"DOMNodeRemoved",function(b){b.target==
-a.win[0]&&a.remove()})));!a.ispage&&a.opt.boxzoom&&a.bind(window,"resize",a.resizeZoom);a.istextarea&&a.bind(a.win,"mouseup",a.lazyResize);a.lazyResize(30)}if("IFRAME"==this.doc[0].nodeName){var M=function(){a.iframexd=!1;var b;try{b="contentDocument"in this?this.contentDocument:this.contentWindow.document}catch(c){a.iframexd=!0,b=!1}if(a.iframexd)return"console"in window&&console.log("NiceScroll error: policy restriced iframe"),!0;a.forcescreen=!0;a.isiframe&&(a.iframe={doc:f(b),html:a.doc.contents().find("html")[0],
-body:a.doc.contents().find("body")[0]},a.getContentSize=function(){return{w:Math.max(a.iframe.html.scrollWidth,a.iframe.body.scrollWidth),h:Math.max(a.iframe.html.scrollHeight,a.iframe.body.scrollHeight)}},a.docscroll=f(a.iframe.body));if(!e.isios&&a.opt.iframeautoresize&&!a.isiframe){a.win.scrollTop(0);a.doc.height("");var g=Math.max(b.getElementsByTagName("html")[0].scrollHeight,b.body.scrollHeight);a.doc.height(g)}a.lazyResize(30);e.isie7&&a.css(f(a.iframe.html),{"overflow-y":"hidden"});a.css(f(a.iframe.body),
-{"overflow-y":"hidden"});e.isios&&a.haswrapper&&a.css(f(b.body),{"-webkit-transform":"translate3d(0,0,0)"});"contentWindow"in this?a.bind(this.contentWindow,"scroll",a.onscroll):a.bind(b,"scroll",a.onscroll);a.opt.enablemousewheel&&a.bind(b,"mousewheel",a.onmousewheel);a.opt.enablekeyboard&&a.bind(b,e.isopera?"keypress":"keydown",a.onkeypress);if(e.cantouch||a.opt.touchbehavior)a.bind(b,"mousedown",a.ontouchstart),a.bind(b,"mousemove",function(b){return a.ontouchmove(b,!0)}),a.opt.grabcursorenabled&&
-e.cursorgrabvalue&&a.css(f(b.body),{cursor:e.cursorgrabvalue});a.bind(b,"mouseup",a.ontouchend);a.zoom&&(a.opt.dblclickzoom&&a.bind(b,"dblclick",a.doZoom),a.ongesturezoom&&a.bind(b,"gestureend",a.ongesturezoom))};this.doc[0].readyState&&"complete"==this.doc[0].readyState&&setTimeout(function(){M.call(a.doc[0],!1)},500);a.bind(this.doc,"load",M)}};this.showCursor=function(b,c){a.cursortimeout&&(clearTimeout(a.cursortimeout),a.cursortimeout=0);if(a.rail){a.autohidedom&&(a.autohidedom.stop().css({opacity:a.opt.cursoropacitymax}),
-a.cursoractive=!0);a.rail.drag&&1==a.rail.drag.pt||("undefined"!=typeof b&&!1!==b&&(a.scroll.y=Math.round(1*b/a.scrollratio.y)),"undefined"!=typeof c&&(a.scroll.x=Math.round(1*c/a.scrollratio.x)));a.cursor.css({height:a.cursorheight,top:a.scroll.y});if(a.cursorh){var d=a.hasreversehr?a.scrollvaluemaxw-a.scroll.x:a.scroll.x;!a.rail.align&&a.rail.visibility?a.cursorh.css({width:a.cursorwidth,left:d+a.rail.width}):a.cursorh.css({width:a.cursorwidth,left:d});a.cursoractive=!0}a.zoom&&a.zoom.stop().css({opacity:a.opt.cursoropacitymax})}};
-this.hideCursor=function(b){a.cursortimeout||!a.rail||!a.autohidedom||a.hasmousefocus&&"leave"==a.opt.autohidemode||(a.cursortimeout=setTimeout(function(){a.rail.active&&a.showonmouseevent||(a.autohidedom.stop().animate({opacity:a.opt.cursoropacitymin}),a.zoom&&a.zoom.stop().animate({opacity:a.opt.cursoropacitymin}),a.cursoractive=!1);a.cursortimeout=0},b||a.opt.hidecursordelay))};this.noticeCursor=function(b,c,d){a.showCursor(c,d);a.rail.active||a.hideCursor(b)};this.getContentSize=a.ispage?function(){return{w:Math.max(document.body.scrollWidth,
-document.documentElement.scrollWidth),h:Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}}:a.haswrapper?function(){return{w:a.doc.outerWidth()+parseInt(a.win.css("paddingLeft"))+parseInt(a.win.css("paddingRight")),h:a.doc.outerHeight()+parseInt(a.win.css("paddingTop"))+parseInt(a.win.css("paddingBottom"))}}:function(){return{w:a.docscroll[0].scrollWidth,h:a.docscroll[0].scrollHeight}};this.onResize=function(b,c){if(!a||!a.win)return!1;if(!a.haswrapper&&!a.ispage){if("none"==
-a.win.css("display"))return a.visibility&&a.hideRail().hideRailHr(),!1;a.hidden||a.visibility||a.showRail().showRailHr()}var d=a.page.maxh,e=a.page.maxw,f=a.view.h,h=a.view.w;a.view={w:a.ispage?a.win.width():parseInt(a.win[0].clientWidth),h:a.ispage?a.win.height():parseInt(a.win[0].clientHeight)};a.page=c?c:a.getContentSize();a.page.maxh=Math.max(0,a.page.h-a.view.h);a.page.maxw=Math.max(0,a.page.w-a.view.w);if(a.page.maxh==d&&a.page.maxw==e&&a.view.w==h&&a.view.h==f){if(a.ispage)return a;d=a.win.offset();
-if(a.lastposition&&(e=a.lastposition,e.top==d.top&&e.left==d.left))return a;a.lastposition=d}0==a.page.maxh?(a.hideRail(),a.scrollvaluemax=0,a.scroll.y=0,a.scrollratio.y=0,a.cursorheight=0,a.setScrollTop(0),a.rail.scrollable=!1):(a.page.maxh-=a.opt.railpadding.top+a.opt.railpadding.bottom,a.rail.scrollable=!0);0==a.page.maxw?(a.hideRailHr(),a.scrollvaluemaxw=0,a.scroll.x=0,a.scrollratio.x=0,a.cursorwidth=0,a.setScrollLeft(0),a.railh.scrollable=!1):(a.page.maxw-=a.opt.railpadding.left+a.opt.railpadding.right,
-a.railh.scrollable=!0);a.railslocked=a.locked||0==a.page.maxh&&0==a.page.maxw;if(a.railslocked)return a.ispage||a.updateScrollBar(a.view),!1;a.hidden||a.visibility?a.hidden||a.railh.visibility||a.showRailHr():a.showRail().showRailHr();a.istextarea&&a.win.css("resize")&&"none"!=a.win.css("resize")&&(a.view.h-=20);a.cursorheight=Math.min(a.view.h,Math.round(a.view.h/a.page.h*a.view.h));a.cursorheight=a.opt.cursorfixedheight?a.opt.cursorfixedheight:Math.max(a.opt.cursorminheight,a.cursorheight);a.cursorwidth=
-Math.min(a.view.w,Math.round(a.view.w/a.page.w*a.view.w));a.cursorwidth=a.opt.cursorfixedheight?a.opt.cursorfixedheight:Math.max(a.opt.cursorminheight,a.cursorwidth);a.scrollvaluemax=a.view.h-a.cursorheight-a.cursor.hborder-(a.opt.railpadding.top+a.opt.railpadding.bottom);a.railh&&(a.railh.width=0<a.page.maxh?a.view.w-a.rail.width:a.view.w,a.scrollvaluemaxw=a.railh.width-a.cursorwidth-a.cursorh.wborder-(a.opt.railpadding.left+a.opt.railpadding.right));a.ispage||a.updateScrollBar(a.view);a.scrollratio=
-{x:a.page.maxw/a.scrollvaluemaxw,y:a.page.maxh/a.scrollvaluemax};a.getScrollTop()>a.page.maxh?a.doScrollTop(a.page.maxh):(a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y)),a.scroll.x=Math.round(a.getScrollLeft()*(1/a.scrollratio.x)),a.cursoractive&&a.noticeCursor());a.scroll.y&&0==a.getScrollTop()&&a.doScrollTo(Math.floor(a.scroll.y*a.scrollratio.y));return a};this.resize=a.onResize;this.lazyResize=function(b){b=isNaN(b)?30:b;a.debounced("resize",a.resize,b);return a};this.jqbind=function(b,
-c,d){a.events.push({e:b,n:c,f:d,q:!0});f(b).bind(c,d)};this.bind=function(b,c,d,f){var h="jquery"in b?b[0]:b;"mousewheel"==c?window.addEventListener||"onwheel"in document?a._bind(h,"wheel",d,f||!1):(b="undefined"!=typeof document.onmousewheel?"mousewheel":"DOMMouseScroll",n(h,b,d,f||!1),"DOMMouseScroll"==b&&n(h,"MozMousePixelScroll",d,f||!1)):h.addEventListener?(e.cantouch&&/mouseup|mousedown|mousemove/.test(c)&&a._bind(h,"mousedown"==c?"touchstart":"mouseup"==c?"touchend":"touchmove",function(a){if(a.touches){if(2>
-a.touches.length){var b=a.touches.length?a.touches[0]:a;b.original=a;d.call(this,b)}}else a.changedTouches&&(b=a.changedTouches[0],b.original=a,d.call(this,b))},f||!1),a._bind(h,c,d,f||!1),e.cantouch&&"mouseup"==c&&a._bind(h,"touchcancel",d,f||!1)):a._bind(h,c,function(b){(b=b||window.event||!1)&&b.srcElement&&(b.target=b.srcElement);"pageY"in b||(b.pageX=b.clientX+document.documentElement.scrollLeft,b.pageY=b.clientY+document.documentElement.scrollTop);return!1===d.call(h,b)||!1===f?a.cancelEvent(b):
-!0})};e.haseventlistener?(this._bind=function(b,c,d,e){a.events.push({e:b,n:c,f:d,b:e,q:!1});b.addEventListener(c,d,e||!1)},this.cancelEvent=function(a){if(!a)return!1;a=a.original?a.original:a;a.preventDefault();a.stopPropagation();a.preventManipulation&&a.preventManipulation();return!1},this.stopPropagation=function(a){if(!a)return!1;a=a.original?a.original:a;a.stopPropagation();return!1},this._unbind=function(a,c,d,e){a.removeEventListener(c,d,e)}):(this._bind=function(b,c,d,e){a.events.push({e:b,
-n:c,f:d,b:e,q:!1});b.attachEvent?b.attachEvent("on"+c,d):b["on"+c]=d},this.cancelEvent=function(a){a=window.event||!1;if(!a)return!1;a.cancelBubble=!0;a.cancel=!0;return a.returnValue=!1},this.stopPropagation=function(a){a=window.event||!1;if(!a)return!1;a.cancelBubble=!0;return!1},this._unbind=function(a,c,d,e){a.detachEvent?a.detachEvent("on"+c,d):a["on"+c]=!1});this.unbindAll=function(){for(var b=0;b<a.events.length;b++){var c=a.events[b];c.q?c.e.unbind(c.n,c.f):a._unbind(c.e,c.n,c.f,c.b)}};this.showRail=
-function(){0==a.page.maxh||!a.ispage&&"none"==a.win.css("display")||(a.visibility=!0,a.rail.visibility=!0,a.rail.css("display","block"));return a};this.showRailHr=function(){if(!a.railh)return a;0==a.page.maxw||!a.ispage&&"none"==a.win.css("display")||(a.railh.visibility=!0,a.railh.css("display","block"));return a};this.hideRail=function(){a.visibility=!1;a.rail.visibility=!1;a.rail.css("display","none");return a};this.hideRailHr=function(){if(!a.railh)return a;a.railh.visibility=!1;a.railh.css("display",
-"none");return a};this.show=function(){a.hidden=!1;a.railslocked=!1;return a.showRail().showRailHr()};this.hide=function(){a.hidden=!0;a.railslocked=!0;return a.hideRail().hideRailHr()};this.toggle=function(){return a.hidden?a.show():a.hide()};this.remove=function(){a.stop();a.cursortimeout&&clearTimeout(a.cursortimeout);a.doZoomOut();a.unbindAll();e.isie9&&a.win[0].detachEvent("onpropertychange",a.onAttributeChange);!1!==a.observer&&a.observer.disconnect();!1!==a.observerremover&&a.observerremover.disconnect();
-!1!==a.observerbody&&a.observerbody.disconnect();a.events=null;a.cursor&&a.cursor.remove();a.cursorh&&a.cursorh.remove();a.rail&&a.rail.remove();a.railh&&a.railh.remove();a.zoom&&a.zoom.remove();for(var b=0;b<a.saved.css.length;b++){var c=a.saved.css[b];c[0].css(c[1],"undefined"==typeof c[2]?"":c[2])}a.saved=!1;a.me.data("__nicescroll","");var d=f.nicescroll;d.each(function(b){if(this&&this.id===a.id){delete d[b];for(var c=++b;c<d.length;c++,b++)d[b]=d[c];d.length--;d.length&&delete d[d.length]}});
-for(var h in a)a[h]=null,delete a[h];a=null};this.scrollstart=function(b){this.onscrollstart=b;return a};this.scrollend=function(b){this.onscrollend=b;return a};this.scrollcancel=function(b){this.onscrollcancel=b;return a};this.zoomin=function(b){this.onzoomin=b;return a};this.zoomout=function(b){this.onzoomout=b;return a};this.isScrollable=function(a){a=a.target?a.target:a;if("OPTION"==a.nodeName)return!0;for(;a&&1==a.nodeType&&!/^BODY|HTML/.test(a.nodeName);){var c=f(a),c=c.css("overflowY")||c.css("overflowX")||
-c.css("overflow")||"";if(/scroll|auto/.test(c))return a.clientHeight!=a.scrollHeight;a=a.parentNode?a.parentNode:!1}return!1};this.getViewport=function(a){for(a=a&&a.parentNode?a.parentNode:!1;a&&1==a.nodeType&&!/^BODY|HTML/.test(a.nodeName);){var c=f(a);if(/fixed|absolute/.test(c.css("position")))return c;var d=c.css("overflowY")||c.css("overflowX")||c.css("overflow")||"";if(/scroll|auto/.test(d)&&a.clientHeight!=a.scrollHeight||0<c.getNiceScroll().length)return c;a=a.parentNode?a.parentNode:!1}return!1};
-this.triggerScrollEnd=function(){if(a.onscrollend){var b=a.getScrollLeft(),c=a.getScrollTop();a.onscrollend.call(a,{type:"scrollend",current:{x:b,y:c},end:{x:b,y:c}})}};this.onmousewheel=function(b){if(!a.wheelprevented){if(a.railslocked)return a.debounced("checkunlock",a.resize,250),!0;if(a.rail.drag)return a.cancelEvent(b);"auto"==a.opt.oneaxismousemode&&0!=b.deltaX&&(a.opt.oneaxismousemode=!1);if(a.opt.oneaxismousemode&&0==b.deltaX&&!a.rail.scrollable)return a.railh&&a.railh.scrollable?a.onmousewheelhr(b):
-!0;var c=+new Date,d=!1;a.opt.preservenativescrolling&&a.checkarea+600<c&&(a.nativescrollingarea=a.isScrollable(b),d=!0);a.checkarea=c;if(a.nativescrollingarea)return!0;if(b=p(b,!1,d))a.checkarea=0;return b}};this.onmousewheelhr=function(b){if(!a.wheelprevented){if(a.railslocked||!a.railh.scrollable)return!0;if(a.rail.drag)return a.cancelEvent(b);var c=+new Date,d=!1;a.opt.preservenativescrolling&&a.checkarea+600<c&&(a.nativescrollingarea=a.isScrollable(b),d=!0);a.checkarea=c;return a.nativescrollingarea?
-!0:a.railslocked?a.cancelEvent(b):p(b,!0,d)}};this.stop=function(){a.cancelScroll();a.scrollmon&&a.scrollmon.stop();a.cursorfreezed=!1;a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y));a.noticeCursor();return a};this.getTransitionSpeed=function(b){var c=Math.round(10*a.opt.scrollspeed);b=Math.min(c,Math.round(b/20*a.opt.scrollspeed));return 20<b?b:0};a.opt.smoothscroll?a.ishwscroll&&e.hastransition&&a.opt.usetransition&&a.opt.smoothscroll?(this.prepareTransition=function(b,c){var d=c?20<
-b?b:0:a.getTransitionSpeed(b),f=d?e.prefixstyle+"transform "+d+"ms ease-out":"";a.lasttransitionstyle&&a.lasttransitionstyle==f||(a.lasttransitionstyle=f,a.doc.css(e.transitionstyle,f));return d},this.doScrollLeft=function(b,c){var d=a.scrollrunning?a.newscrolly:a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,c){var d=a.scrollrunning?a.newscrollx:a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){var f=a.getScrollTop(),h=a.getScrollLeft();(0>(a.newscrolly-
-f)*(c-f)||0>(a.newscrollx-h)*(b-h))&&a.cancelScroll();0==a.opt.bouncescroll&&(0>c?c=0:c>a.page.maxh&&(c=a.page.maxh),0>b?b=0:b>a.page.maxw&&(b=a.page.maxw));if(a.scrollrunning&&b==a.newscrollx&&c==a.newscrolly)return!1;a.newscrolly=c;a.newscrollx=b;a.newscrollspeed=d||!1;if(a.timer)return!1;a.timer=setTimeout(function(){var d=a.getScrollTop(),f=a.getScrollLeft(),h,k;h=b-f;k=c-d;h=Math.round(Math.sqrt(Math.pow(h,2)+Math.pow(k,2)));h=a.newscrollspeed&&1<a.newscrollspeed?a.newscrollspeed:a.getTransitionSpeed(h);
-a.newscrollspeed&&1>=a.newscrollspeed&&(h*=a.newscrollspeed);a.prepareTransition(h,!0);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);0<h&&(!a.scrollrunning&&a.onscrollstart&&a.onscrollstart.call(a,{type:"scrollstart",current:{x:f,y:d},request:{x:b,y:c},end:{x:a.newscrollx,y:a.newscrolly},speed:h}),e.transitionend?a.scrollendtrapped||(a.scrollendtrapped=!0,a.bind(a.doc,e.transitionend,a.onScrollTransitionEnd,!1)):(a.scrollendtrapped&&clearTimeout(a.scrollendtrapped),a.scrollendtrapped=
-setTimeout(a.onScrollTransitionEnd,h)),a.timerscroll={bz:new A(d,a.newscrolly,h,0,0,.58,1),bh:new A(f,a.newscrollx,h,0,0,.58,1)},a.cursorfreezed||(a.timerscroll.tm=setInterval(function(){a.showCursor(a.getScrollTop(),a.getScrollLeft())},60)));a.synched("doScroll-set",function(){a.timer=0;a.scrollendtrapped&&(a.scrollrunning=!0);a.setScrollTop(a.newscrolly);a.setScrollLeft(a.newscrollx);if(!a.scrollendtrapped)a.onScrollTransitionEnd()})},50)},this.cancelScroll=function(){if(!a.scrollendtrapped)return!0;
-var b=a.getScrollTop(),c=a.getScrollLeft();a.scrollrunning=!1;e.transitionend||clearTimeout(e.transitionend);a.scrollendtrapped=!1;a._unbind(a.doc[0],e.transitionend,a.onScrollTransitionEnd);a.prepareTransition(0);a.setScrollTop(b);a.railh&&a.setScrollLeft(c);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);a.timerscroll=!1;a.cursorfreezed=!1;a.showCursor(b,c);return a},this.onScrollTransitionEnd=function(){a.scrollendtrapped&&a._unbind(a.doc[0],e.transitionend,a.onScrollTransitionEnd);
-a.scrollendtrapped=!1;a.prepareTransition(0);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);a.timerscroll=!1;var b=a.getScrollTop(),c=a.getScrollLeft();a.setScrollTop(b);a.railh&&a.setScrollLeft(c);a.noticeCursor(!1,b,c);a.cursorfreezed=!1;0>b?b=0:b>a.page.maxh&&(b=a.page.maxh);0>c?c=0:c>a.page.maxw&&(c=a.page.maxw);if(b!=a.newscrolly||c!=a.newscrollx)return a.doScrollPos(c,b,a.opt.snapbackspeed);a.onscrollend&&a.scrollrunning&&a.triggerScrollEnd();a.scrollrunning=!1}):(this.doScrollLeft=
-function(b,c){var d=a.scrollrunning?a.newscrolly:a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,c){var d=a.scrollrunning?a.newscrollx:a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){function e(){if(a.cancelAnimationFrame)return!0;a.scrollrunning=!0;if(n=1-n)return a.timer=s(e)||1;var b=0,c,d,g=d=a.getScrollTop();if(a.dst.ay){g=a.bzscroll?a.dst.py+a.bzscroll.getNow()*a.dst.ay:a.newscrolly;c=g-d;if(0>c&&g<a.newscrolly||0<c&&g>a.newscrolly)g=a.newscrolly;
-a.setScrollTop(g);g==a.newscrolly&&(b=1)}else b=1;d=c=a.getScrollLeft();if(a.dst.ax){d=a.bzscroll?a.dst.px+a.bzscroll.getNow()*a.dst.ax:a.newscrollx;c=d-c;if(0>c&&d<a.newscrollx||0<c&&d>a.newscrollx)d=a.newscrollx;a.setScrollLeft(d);d==a.newscrollx&&(b+=1)}else b+=1;2==b?(a.timer=0,a.cursorfreezed=!1,a.bzscroll=!1,a.scrollrunning=!1,0>g?g=0:g>a.page.maxh&&(g=a.page.maxh),0>d?d=0:d>a.page.maxw&&(d=a.page.maxw),d!=a.newscrollx||g!=a.newscrolly?a.doScrollPos(d,g):a.onscrollend&&a.triggerScrollEnd()):
-a.timer=s(e)||1}c="undefined"==typeof c||!1===c?a.getScrollTop(!0):c;if(a.timer&&a.newscrolly==c&&a.newscrollx==b)return!0;a.timer&&t(a.timer);a.timer=0;var f=a.getScrollTop(),h=a.getScrollLeft();(0>(a.newscrolly-f)*(c-f)||0>(a.newscrollx-h)*(b-h))&&a.cancelScroll();a.newscrolly=c;a.newscrollx=b;a.bouncescroll&&a.rail.visibility||(0>a.newscrolly?a.newscrolly=0:a.newscrolly>a.page.maxh&&(a.newscrolly=a.page.maxh));a.bouncescroll&&a.railh.visibility||(0>a.newscrollx?a.newscrollx=0:a.newscrollx>a.page.maxw&&
-(a.newscrollx=a.page.maxw));a.dst={};a.dst.x=b-h;a.dst.y=c-f;a.dst.px=h;a.dst.py=f;var k=Math.round(Math.sqrt(Math.pow(a.dst.x,2)+Math.pow(a.dst.y,2)));a.dst.ax=a.dst.x/k;a.dst.ay=a.dst.y/k;var l=0,m=k;0==a.dst.x?(l=f,m=c,a.dst.ay=1,a.dst.py=0):0==a.dst.y&&(l=h,m=b,a.dst.ax=1,a.dst.px=0);k=a.getTransitionSpeed(k);d&&1>=d&&(k*=d);a.bzscroll=0<k?a.bzscroll?a.bzscroll.update(m,k):new A(l,m,k,0,1,0,1):!1;if(!a.timer){(f==a.page.maxh&&c>=a.page.maxh||h==a.page.maxw&&b>=a.page.maxw)&&a.checkContentSize();
-var n=1;a.cancelAnimationFrame=!1;a.timer=1;a.onscrollstart&&!a.scrollrunning&&a.onscrollstart.call(a,{type:"scrollstart",current:{x:h,y:f},request:{x:b,y:c},end:{x:a.newscrollx,y:a.newscrolly},speed:k});e();(f==a.page.maxh&&c>=f||h==a.page.maxw&&b>=h)&&a.checkContentSize();a.noticeCursor()}},this.cancelScroll=function(){a.timer&&t(a.timer);a.timer=0;a.bzscroll=!1;a.scrollrunning=!1;return a}):(this.doScrollLeft=function(b,c){var d=a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,
-c){var d=a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){var e=b>a.page.maxw?a.page.maxw:b;0>e&&(e=0);var f=c>a.page.maxh?a.page.maxh:c;0>f&&(f=0);a.synched("scroll",function(){a.setScrollTop(f);a.setScrollLeft(e)})},this.cancelScroll=function(){});this.doScrollBy=function(b,c){var d=0,d=c?Math.floor((a.scroll.y-b)*a.scrollratio.y):(a.timer?a.newscrolly:a.getScrollTop(!0))-b;if(a.bouncescroll){var e=Math.round(a.view.h/2);d<-e?d=-e:d>a.page.maxh+e&&(d=a.page.maxh+e)}a.cursorfreezed=
-!1;e=a.getScrollTop(!0);if(0>d&&0>=e)return a.noticeCursor();if(d>a.page.maxh&&e>=a.page.maxh)return a.checkContentSize(),a.noticeCursor();a.doScrollTop(d)};this.doScrollLeftBy=function(b,c){var d=0,d=c?Math.floor((a.scroll.x-b)*a.scrollratio.x):(a.timer?a.newscrollx:a.getScrollLeft(!0))-b;if(a.bouncescroll){var e=Math.round(a.view.w/2);d<-e?d=-e:d>a.page.maxw+e&&(d=a.page.maxw+e)}a.cursorfreezed=!1;e=a.getScrollLeft(!0);if(0>d&&0>=e||d>a.page.maxw&&e>=a.page.maxw)return a.noticeCursor();a.doScrollLeft(d)};
-this.doScrollTo=function(b,c){c&&Math.round(b*a.scrollratio.y);a.cursorfreezed=!1;a.doScrollTop(b)};this.checkContentSize=function(){var b=a.getContentSize();b.h==a.page.h&&b.w==a.page.w||a.resize(!1,b)};a.onscroll=function(b){a.rail.drag||a.cursorfreezed||a.synched("scroll",function(){a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y));a.railh&&(a.scroll.x=Math.round(a.getScrollLeft()*(1/a.scrollratio.x)));a.noticeCursor()})};a.bind(a.docscroll,"scroll",a.onscroll);this.doZoomIn=function(b){if(!a.zoomactive){a.zoomactive=
-!0;a.zoomrestore={style:{}};var c="position top left zIndex backgroundColor marginTop marginBottom marginLeft marginRight".split(" "),d=a.win[0].style,h;for(h in c){var k=c[h];a.zoomrestore.style[k]="undefined"!=typeof d[k]?d[k]:""}a.zoomrestore.style.width=a.win.css("width");a.zoomrestore.style.height=a.win.css("height");a.zoomrestore.padding={w:a.win.outerWidth()-a.win.width(),h:a.win.outerHeight()-a.win.height()};e.isios4&&(a.zoomrestore.scrollTop=f(window).scrollTop(),f(window).scrollTop(0));
-a.win.css({position:e.isios4?"absolute":"fixed",top:0,left:0,"z-index":x+100,margin:"0px"});c=a.win.css("backgroundColor");(""==c||/transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(c))&&a.win.css("backgroundColor","#fff");a.rail.css({"z-index":x+101});a.zoom.css({"z-index":x+102});a.zoom.css("backgroundPosition","0px -18px");a.resizeZoom();a.onzoomin&&a.onzoomin.call(a);return a.cancelEvent(b)}};this.doZoomOut=function(b){if(a.zoomactive)return a.zoomactive=!1,a.win.css("margin",""),a.win.css(a.zoomrestore.style),
-e.isios4&&f(window).scrollTop(a.zoomrestore.scrollTop),a.rail.css({"z-index":a.zindex}),a.zoom.css({"z-index":a.zindex}),a.zoomrestore=!1,a.zoom.css("backgroundPosition","0px 0px"),a.onResize(),a.onzoomout&&a.onzoomout.call(a),a.cancelEvent(b)};this.doZoom=function(b){return a.zoomactive?a.doZoomOut(b):a.doZoomIn(b)};this.resizeZoom=function(){if(a.zoomactive){var b=a.getScrollTop();a.win.css({width:f(window).width()-a.zoomrestore.padding.w+"px",height:f(window).height()-a.zoomrestore.padding.h+"px"});
-a.onResize();a.setScrollTop(Math.min(a.page.maxh,b))}};this.init();f.nicescroll.push(this)},L=function(f){var c=this;this.nc=f;this.steptime=this.lasttime=this.speedy=this.speedx=this.lasty=this.lastx=0;this.snapy=this.snapx=!1;this.demuly=this.demulx=0;this.lastscrolly=this.lastscrollx=-1;this.timer=this.chky=this.chkx=0;this.time=function(){return+new Date};this.reset=function(f,k){c.stop();var d=c.time();c.steptime=0;c.lasttime=d;c.speedx=0;c.speedy=0;c.lastx=f;c.lasty=k;c.lastscrollx=-1;c.lastscrolly=
--1};this.update=function(f,k){var d=c.time();c.steptime=d-c.lasttime;c.lasttime=d;var d=k-c.lasty,n=f-c.lastx,p=c.nc.getScrollTop(),a=c.nc.getScrollLeft(),p=p+d,a=a+n;c.snapx=0>a||a>c.nc.page.maxw;c.snapy=0>p||p>c.nc.page.maxh;c.speedx=n;c.speedy=d;c.lastx=f;c.lasty=k};this.stop=function(){c.nc.unsynched("domomentum2d");c.timer&&clearTimeout(c.timer);c.timer=0;c.lastscrollx=-1;c.lastscrolly=-1};this.doSnapy=function(f,k){var d=!1;0>k?(k=0,d=!0):k>c.nc.page.maxh&&(k=c.nc.page.maxh,d=!0);0>f?(f=0,d=
-!0):f>c.nc.page.maxw&&(f=c.nc.page.maxw,d=!0);d?c.nc.doScrollPos(f,k,c.nc.opt.snapbackspeed):c.nc.triggerScrollEnd()};this.doMomentum=function(f){var k=c.time(),d=f?k+f:c.lasttime;f=c.nc.getScrollLeft();var n=c.nc.getScrollTop(),p=c.nc.page.maxh,a=c.nc.page.maxw;c.speedx=0<a?Math.min(60,c.speedx):0;c.speedy=0<p?Math.min(60,c.speedy):0;d=d&&60>=k-d;if(0>n||n>p||0>f||f>a)d=!1;f=c.speedx&&d?c.speedx:!1;if(c.speedy&&d&&c.speedy||f){var s=Math.max(16,c.steptime);50<s&&(f=s/50,c.speedx*=f,c.speedy*=f,s=
-50);c.demulxy=0;c.lastscrollx=c.nc.getScrollLeft();c.chkx=c.lastscrollx;c.lastscrolly=c.nc.getScrollTop();c.chky=c.lastscrolly;var e=c.lastscrollx,r=c.lastscrolly,t=function(){var d=600<c.time()-k?.04:.02;c.speedx&&(e=Math.floor(c.lastscrollx-c.speedx*(1-c.demulxy)),c.lastscrollx=e,0>e||e>a)&&(d=.1);c.speedy&&(r=Math.floor(c.lastscrolly-c.speedy*(1-c.demulxy)),c.lastscrolly=r,0>r||r>p)&&(d=.1);c.demulxy=Math.min(1,c.demulxy+d);c.nc.synched("domomentum2d",function(){c.speedx&&(c.nc.getScrollLeft()!=
-c.chkx&&c.stop(),c.chkx=e,c.nc.setScrollLeft(e));c.speedy&&(c.nc.getScrollTop()!=c.chky&&c.stop(),c.chky=r,c.nc.setScrollTop(r));c.timer||(c.nc.hideCursor(),c.doSnapy(e,r))});1>c.demulxy?c.timer=setTimeout(t,s):(c.stop(),c.nc.hideCursor(),c.doSnapy(e,r))};t()}else c.doSnapy(c.nc.getScrollLeft(),c.nc.getScrollTop())}},w=f.fn.scrollTop;f.cssHooks.pageYOffset={get:function(k,c,h){return(c=f.data(k,"__nicescroll")||!1)&&c.ishwscroll?c.getScrollTop():w.call(k)},set:function(k,c){var h=f.data(k,"__nicescroll")||
-!1;h&&h.ishwscroll?h.setScrollTop(parseInt(c)):w.call(k,c);return this}};f.fn.scrollTop=function(k){if("undefined"==typeof k){var c=this[0]?f.data(this[0],"__nicescroll")||!1:!1;return c&&c.ishwscroll?c.getScrollTop():w.call(this)}return this.each(function(){var c=f.data(this,"__nicescroll")||!1;c&&c.ishwscroll?c.setScrollTop(parseInt(k)):w.call(f(this),k)})};var B=f.fn.scrollLeft;f.cssHooks.pageXOffset={get:function(k,c,h){return(c=f.data(k,"__nicescroll")||!1)&&c.ishwscroll?c.getScrollLeft():B.call(k)},
-set:function(k,c){var h=f.data(k,"__nicescroll")||!1;h&&h.ishwscroll?h.setScrollLeft(parseInt(c)):B.call(k,c);return this}};f.fn.scrollLeft=function(k){if("undefined"==typeof k){var c=this[0]?f.data(this[0],"__nicescroll")||!1:!1;return c&&c.ishwscroll?c.getScrollLeft():B.call(this)}return this.each(function(){var c=f.data(this,"__nicescroll")||!1;c&&c.ishwscroll?c.setScrollLeft(parseInt(k)):B.call(f(this),k)})};var C=function(k){var c=this;this.length=0;this.name="nicescrollarray";this.each=function(d){for(var f=
-0,h=0;f<c.length;f++)d.call(c[f],h++);return c};this.push=function(d){c[c.length]=d;c.length++};this.eq=function(d){return c[d]};if(k)for(var h=0;h<k.length;h++){var m=f.data(k[h],"__nicescroll")||!1;m&&(this[this.length]=m,this.length++)}return this};(function(f,c,h){for(var m=0;m<c.length;m++)h(f,c[m])})(C.prototype,"show hide toggle onResize resize remove stop doScrollPos".split(" "),function(f,c){f[c]=function(){var f=arguments;return this.each(function(){this[c].apply(this,f)})}});f.fn.getNiceScroll=
-function(k){return"undefined"==typeof k?new C(this):this[k]&&f.data(this[k],"__nicescroll")||!1};f.extend(f.expr[":"],{nicescroll:function(k){return f.data(k,"__nicescroll")?!0:!1}});f.fn.niceScroll=function(k,c){"undefined"!=typeof c||"object"!=typeof k||"jquery"in k||(c=k,k=!1);c=f.extend({},c);var h=new C;"undefined"==typeof c&&(c={});k&&(c.doc=f(k),c.win=f(this));var m=!("doc"in c);m||"win"in c||(c.win=f(this));this.each(function(){var d=f(this).data("__nicescroll")||!1;d||(c.doc=m?f(this):c.doc,
-d=new R(c,f(this)),f(this).data("__nicescroll",d));h.push(d)});return 1==h.length?h[0]:h};window.NiceScroll={getjQuery:function(){return f}};f.nicescroll||(f.nicescroll=new C,f.nicescroll.options=I)});