diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 70643f2e1e0feb63a825eabc74016d95f5830eb8..e3784fd7039c56f2ef96a5fcbac2e7a14a38247b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -90,6 +90,7 @@ spec:other:
 spinach:project:half:
   stage: test
   script:
+    - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
     - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
   tags:
     - ruby
@@ -98,6 +99,7 @@ spinach:project:half:
 spinach:project:rest:
   stage: test
   script:
+    - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
     - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
   tags:
     - ruby
@@ -106,6 +108,7 @@ spinach:project:rest:
 spinach:other:
   stage: test
   script:
+    - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
     - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
   tags:
     - ruby
@@ -276,6 +279,7 @@ spinach:project:half:ruby22:
   only:
   - master
   script:
+    - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
     - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
   cache:
     key: "ruby22"
@@ -291,6 +295,7 @@ spinach:project:rest:ruby22:
   only:
   - master
   script:
+    - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
     - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
   cache:
     key: "ruby22"
@@ -306,6 +311,7 @@ spinach:other:ruby22:
   only:
   - master
   script:
+    - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
     - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
   cache:
     key: "ruby22"
@@ -319,7 +325,7 @@ spinach:other:ruby22:
 notify:slack:
   stage: notifications
   script:
-    - ./scripts/notify_slack.sh "#builds" "Build failed for master/tags!"
+    - ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
   when: on_failure
   only:
     - master@gitlab-org/gitlab-ce
diff --git a/CHANGELOG b/CHANGELOG
index c38af6c74be6897f61517ee5896e8ac935735931..d63385908d475869dbd94521d9740e61dd653159 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,7 +1,16 @@
 Please view this file on the master branch, on stable branches it's out of date.
 
 v 8.6.0 (unreleased)
+  - Contributions to forked projects are included in calendar
   - Improve the formatting for the user page bio (Connor Shea)
+  - Fix issue when pushing to projects ending in .wiki
+  - Fix avatar stretching by providing a cropping feature (Johann Pardanaud)
+  - Strip leading and trailing spaces in URL validator (evuez)
+  - Update documentation to reflect Guest role not being enforced on internal projects
+
+v 8.5.2
+  - Fix sidebar overlapping content when screen width was below 1200px
+  - Fix error 500 when commenting on a commit
 
 v 8.5.1
   - Fix group projects styles
@@ -21,9 +30,7 @@ v 8.5.1
   - Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu)
   - Update sentry-raven gem to 0.15.6
   - Add build coverage in project's builds page (Steffen Köhler)
-
-v 8.5.2
-  - Fix error 500 when commenting on a commit
+  - Changed # to ! for merge requests in activity view
 
 v 8.5.1
   - Fix group projects styles
diff --git a/Gemfile b/Gemfile
index b15f97e02e56f04baddb451641a925fbe5d55321..2ce00596748f92bc385b1a6754e4ba174c2227bf 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,6 @@
 source "https://rubygems.org"
 
-gem 'rails', '4.2.5.1'
+gem 'rails', '4.2.5.2'
 gem 'rails-deprecated_sanitizer', '~> 1.0.3'
 
 # Responders respond_to and respond_with
@@ -82,6 +82,9 @@ gem "haml-rails", '~> 0.9.0'
 # Files attachments
 gem "carrierwave", '~> 0.9.0'
 
+# Image editing
+gem "mini_magick", '~> 4.4.0'
+
 # Drag and Drop UI
 gem 'dropzonejs-rails', '~> 0.7.1'
 
@@ -220,7 +223,6 @@ gem 'jquery-atwho-rails', '~> 1.3.2'
 gem 'jquery-rails',       '~> 4.0.0'
 gem 'jquery-scrollto-rails', '~> 1.4.3'
 gem 'jquery-ui-rails',    '~> 5.0.0'
-gem 'nprogress-rails',    '~> 0.1.6.7'
 gem 'raphael-rails',      '~> 2.1.2'
 gem 'request_store',      '~> 1.2.0'
 gem 'select2-rails',      '~> 3.5.9'
diff --git a/Gemfile.lock b/Gemfile.lock
index 141e49972a7ad8e1d1c368d072765953f97de67e..0f197bdb402a39afaa5b5406e618edf8cabbce77 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.5.1)
-      actionpack (= 4.2.5.1)
-      actionview (= 4.2.5.1)
-      activejob (= 4.2.5.1)
+    actionmailer (4.2.5.2)
+      actionpack (= 4.2.5.2)
+      actionview (= 4.2.5.2)
+      activejob (= 4.2.5.2)
       mail (~> 2.5, >= 2.5.4)
       rails-dom-testing (~> 1.0, >= 1.0.5)
-    actionpack (4.2.5.1)
-      actionview (= 4.2.5.1)
-      activesupport (= 4.2.5.1)
+    actionpack (4.2.5.2)
+      actionview (= 4.2.5.2)
+      activesupport (= 4.2.5.2)
       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.5.1)
-      activesupport (= 4.2.5.1)
+    actionview (4.2.5.2)
+      activesupport (= 4.2.5.2)
       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.5.1)
-      activesupport (= 4.2.5.1)
+    activejob (4.2.5.2)
+      activesupport (= 4.2.5.2)
       globalid (>= 0.3.0)
-    activemodel (4.2.5.1)
-      activesupport (= 4.2.5.1)
+    activemodel (4.2.5.2)
+      activesupport (= 4.2.5.2)
       builder (~> 3.1)
-    activerecord (4.2.5.1)
-      activemodel (= 4.2.5.1)
-      activesupport (= 4.2.5.1)
+    activerecord (4.2.5.2)
+      activemodel (= 4.2.5.2)
+      activesupport (= 4.2.5.2)
       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.5.1)
+    activesupport (4.2.5.2)
       i18n (~> 0.7)
       json (~> 1.7, >= 1.7.7)
       minitest (~> 5.1)
@@ -492,6 +492,7 @@ GEM
     method_source (0.8.2)
     mime-types (1.25.1)
     mimemagic (0.3.0)
+    mini_magick (4.4.0)
     mini_portile2 (2.0.0)
     minitest (5.7.0)
     mousetrap-rails (1.4.6)
@@ -506,7 +507,6 @@ GEM
     newrelic_rpm (3.14.1.311)
     nokogiri (1.6.7.2)
       mini_portile2 (~> 2.0.0.rc2)
-    nprogress-rails (0.1.6.7)
     oauth (0.4.7)
     oauth2 (1.0.0)
       faraday (>= 0.8, < 0.10)
@@ -610,16 +610,16 @@ GEM
       rack
     rack-test (0.6.3)
       rack (>= 1.0)
-    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)
+    rails (4.2.5.2)
+      actionmailer (= 4.2.5.2)
+      actionpack (= 4.2.5.2)
+      actionview (= 4.2.5.2)
+      activejob (= 4.2.5.2)
+      activemodel (= 4.2.5.2)
+      activerecord (= 4.2.5.2)
+      activesupport (= 4.2.5.2)
       bundler (>= 1.3.0, < 2.0)
-      railties (= 4.2.5.1)
+      railties (= 4.2.5.2)
       sprockets-rails
     rails-deprecated_sanitizer (1.0.3)
       activesupport (>= 4.2.0.alpha)
@@ -629,9 +629,9 @@ GEM
       rails-deprecated_sanitizer (>= 1.0.1)
     rails-html-sanitizer (1.0.3)
       loofah (~> 2.0)
-    railties (4.2.5.1)
-      actionpack (= 4.2.5.1)
-      activesupport (= 4.2.5.1)
+    railties (4.2.5.2)
+      actionpack (= 4.2.5.2)
+      activesupport (= 4.2.5.2)
       rake (>= 0.8.7)
       thor (>= 0.18.1, < 2.0)
     rainbow (2.0.0)
@@ -987,6 +987,7 @@ DEPENDENCIES
   loofah (~> 2.0.3)
   mail_room (~> 0.6.1)
   method_source (~> 0.8)
+  mini_magick (~> 4.4.0)
   minitest (~> 5.7.0)
   mousetrap-rails (~> 1.4.6)
   mysql2 (~> 0.3.16)
@@ -995,7 +996,6 @@ DEPENDENCIES
   net-ssh (~> 3.0.1)
   newrelic_rpm (~> 3.14)
   nokogiri (~> 1.6.7, >= 1.6.7.2)
-  nprogress-rails (~> 0.1.6.7)
   oauth2 (~> 1.0.0)
   octokit (~> 3.8.0)
   omniauth (~> 1.3.1)
@@ -1020,7 +1020,7 @@ DEPENDENCIES
   rack-attack (~> 4.3.1)
   rack-cors (~> 0.4.0)
   rack-oauth2 (~> 1.2.1)
-  rails (= 4.2.5.1)
+  rails (= 4.2.5.2)
   rails-deprecated_sanitizer (~> 1.0.3)
   raphael-rails (~> 2.1.2)
   rblineprof
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index b0fd5af9e92f1fa036176b0a2732fed6edad3edc..2c2df95f5adca274651927e1bc30c3afc3506d07 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -32,8 +32,6 @@
 #= require ace/ace
 #= require ace/ext-searchbox
 #= require underscore
-#= require nprogress
-#= require nprogress-turbolinks
 #= require dropzone
 #= require mousetrap
 #= require mousetrap/pause
@@ -45,6 +43,7 @@
 #= require jquery.nicescroll
 #= require_tree .
 #= require fuzzaldrin-plus
+#= require cropper.js
 
 window.slugify = (text) ->
   text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
diff --git a/app/assets/javascripts/autosave.js.coffee b/app/assets/javascripts/autosave.js.coffee
index 5d3fe81da74aefe071aaf29f905d29f39a898859..28f8e103664110f8ba3bf8c4b6b9a663cb06c734 100644
--- a/app/assets/javascripts/autosave.js.coffee
+++ b/app/assets/javascripts/autosave.js.coffee
@@ -16,11 +16,11 @@ class @Autosave
 
     try
       text = window.localStorage.getItem @key
-    catch
+    catch e
       return
 
     @field.val text if text?.length > 0
-    @field.trigger "input"    
+    @field.trigger "input"
 
   save: ->
     return unless window.localStorage?
@@ -35,5 +35,5 @@ class @Autosave
   reset: ->
     return unless window.localStorage?
 
-    try 
+    try
       window.localStorage.removeItem @key
diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee
index 35b2fbbba0736d135b3426cbfc60e6896894c514..d14b7139237217a1cb1d6c7a5ea94dd3129378ee 100644
--- a/app/assets/javascripts/logo.js.coffee
+++ b/app/assets/javascripts/logo.js.coffee
@@ -1,4 +1,4 @@
-NProgress.configure(showSpinner: false)
+Turbolinks.enableProgressBar();
 
 defaultClass = 'tanuki-shape'
 pieces = [
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 40cfa59a229b2287bda8f284ca333ba6d0fc43d2..23a218b4c7d0c44561a914b116ce0a32fe6b1f74 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -146,6 +146,7 @@ class @MergeRequestTabs
       url: "#{source}.json" + @_location.search
       success: (data) =>
         document.querySelector("div#diffs").innerHTML = data.html
+        $('.js-timeago').timeago()
         $('div#diffs .js-syntax-highlight').syntaxHighlight()
         @expandViewContainer() if @diffViewType() is 'parallel'
         @diffsLoaded = true
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index bb0b66b86e1a91587f157ca9d9d0ef8f4d4cdac1..69d590a7533128dda290369837c05309721493d2 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -16,11 +16,50 @@ class @Profile
     $('.update-notifications').on 'ajax:complete', ->
       $(this).find('.btn-save').enable()
 
-    $('.js-choose-user-avatar-button').bind "click", ->
-      form = $(this).closest("form")
-      form.find(".js-user-avatar-input").click()
+    # Avatar management
+
+    $avatarInput = $('.js-user-avatar-input')
+    $filename = $('.js-avatar-filename')
+    $modalCrop = $('.modal-profile-crop')
+    $modalCropImg = $('.modal-profile-crop-image')
+
+    $('.js-choose-user-avatar-button').on "click", ->
+      $form = $(this).closest("form")
+      $form.find(".js-user-avatar-input").click()
+
+    $modalCrop.on 'shown.bs.modal', ->
+      setTimeout ( -> # The cropper must be asynchronously initialized
+        $modalCropImg.cropper
+          aspectRatio: 1
+          modal: false
+          scalable: false
+          rotatable: false
+          zoomable: false
+
+          crop: (event) ->
+            ['x', 'y'].forEach (key) ->
+              $("#user_avatar_crop_#{key}").val(Math.floor(event[key]))
+            $("#user_avatar_crop_size").val(Math.floor(event.width))
+      ), 0
+
+    $modalCrop.on 'hidden.bs.modal', ->
+      $modalCropImg.attr('src', '').cropper('destroy')
+      $avatarInput.val('')
+      $filename.text($filename.data('label'))
 
-    $('.js-user-avatar-input').bind "change", ->
+    $('.js-upload-user-avatar').on 'click', ->
+      $('.edit_user').submit()
+
+    $avatarInput.on "change", ->
       form = $(this).closest("form")
       filename = $(this).val().replace(/^.*[\\\/]/, '')
-      form.find(".js-avatar-filename").text(filename)
+      $filename.data('label', $filename.text()).text(filename)
+
+      reader = new FileReader
+
+      reader.onload = (event) ->
+        $modalCrop.modal('show')
+        $modalCropImg.attr('src', event.target.result)
+
+      fileData = reader.readAsDataURL(this.files[0])
+
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 0c0451fe4ddffcca924242dc13424067609af4a0..e2d590f4df4e8bd37ffeb1952adc6f7d2f4b0f0e 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -9,6 +9,7 @@
  *= require_self
  *= require dropzone/basic
  *= require cal-heatmap
+ *= require cropper.css
 */
 
 /*
@@ -24,12 +25,6 @@
  */
 @import "framework";
 
-/*
- * NProgress load bar css
- */
-@import 'nprogress';
-@import 'nprogress-bootstrap';
-
 /*
  * Font icons
  */
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index fa7641b1676d236fc9b1b45630b7c5653f26c027..e2a30f5ed34fd3c30ad18f814c277495b92b6a74 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -26,6 +26,7 @@
 @import "framework/mobile.scss";
 @import "framework/nav.scss";
 @import "framework/pagination.scss";
+@import "framework/progress.scss";
 @import "framework/panels.scss";
 @import "framework/selects.scss";
 @import "framework/sidebar.scss";
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 354392d5ec3c4ffe3a8544b42c654f7d2ce80a03..b6a781f79de50b5bb4eab938fda59a749509e61f 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -110,7 +110,20 @@ ul.content-list {
 
   > li {
     border-color: $table-border-color;
-    color: $gl-gray;
+    color: $list-text-color;
+    font-size: $list-font-size;
+
+    .title {
+      color: $list-title-color;
+      font-weight: 600;
+    }
+
+    .description {
+      p {
+        @include str-truncated;
+        margin-bottom: 0;
+      }
+    }
 
     .avatar {
       margin-right: 15px;
@@ -127,13 +140,6 @@ ul.content-list {
   }
 }
 
-.panel > .content-list {
-  li {
-    margin: 0;
-    padding: $gl-padding;
-  }
-}
-
 ul.controls {
   padding-top: 1px;
   float: right;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 1d5000fe38881cdf5b388ab0ebf35caea083e2f8..368bbfe53551c81389dd378498f3e14d2ae5616c 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -41,6 +41,12 @@
   transition: $transition;
 }
 
+@mixin transform($transform) {
+  -webkit-transform: $transform;
+  -ms-transform: $transform;
+  transform: $transform;
+}
+
 /**
  * Prefilled mixins
  * Mixins with fixed values
diff --git a/app/assets/stylesheets/framework/progress.scss b/app/assets/stylesheets/framework/progress.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e9800bd24b53d9e562b5470373170e921e298c64
--- /dev/null
+++ b/app/assets/stylesheets/framework/progress.scss
@@ -0,0 +1,5 @@
+html.turbolinks-progress-bar::before {
+  background-color: $progress-color!important;
+  height: 2px!important;
+  box-shadow: 0 0 10px $progress-color, 0 0 5px $progress-color;
+}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index e0ccd6f100fa3a8ff6fecf9b1f765a9c04bc0841..0596924a8f64d699d8c94d6c85b5e5d936d1795f 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -13,6 +13,19 @@
     transition-duration: .3s;
   }
 
+  .home {
+    z-index: 1;
+    position: absolute;
+    left: 0px;
+  }
+
+  #logo {
+    z-index: 2;
+    position: absolute;
+    width: 58px;
+    cursor: pointer;
+  }
+
   &.right-sidebar-expanded {
     padding-right: $gutter_width;
   }
@@ -74,7 +87,7 @@
           width: 158px;
           float: left;
           margin: 0;
-          margin-left: 14px;
+          margin-left: 50px;
           font-size: 19px;
           line-height: 41px;
           font-weight: normal;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 4d72c7e905e8c602dd3cef17fcb6dd4f8dbbedd7..7834cb0bfa541c9cd99300d161f76b074106b763 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -7,7 +7,7 @@ $gl-header-color: #323232;
 $gl-link-color: #333c48;
 $md-text-color: #444;
 $md-link-color: #3084bb;
-$nprogress-color: #c0392b;
+$progress-color: #c0392b;
 $gl-font-size: 15px;
 $list-font-size: 15px;
 $sidebar_collapsed_width: 62px;
@@ -32,6 +32,8 @@ $gl-avatar-size: 40px;
 $secondary-text: #7f8fa4;
 $error-exclamation-point: #E62958;
 $border-radius-default: 3px;
+$list-title-color: #333333;
+$list-text-color: #555555;
 
 /*
  * Color schema
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 8694bd654a745485ab8add2044b2e40448d49417..a2ca00234edc4b230dd1a21ae48b9a4603f401fe 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -4,13 +4,7 @@
     position: relative;
 
     .issue-title {
-      margin-bottom: 5px;
-      font-size: $list-font-size;
-      font-weight: 600;
-    }
-
-    .issue-info {
-      color: $gl-gray;
+      margin-bottom: 2px;
     }
 
     .issue-check {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 9a2c4b83ffbb48d49cc258e23a646b5316012338..2772623f4bde7dc35d297abe5d00bf21fc8f4c23 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -148,15 +148,8 @@
     position: relative;
 
     .merge-request-title {
-      margin-bottom: 5px;
-      font-size: $list-font-size;
-      font-weight: 600;
-    }
-
-    .merge-request-info {
-      color: $gl-gray;
+      margin-bottom: 2px;
     }
-
   }
 
   .merge-request-labels {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 19ead07c06ae159039a21dfbfa897e989e25176a..d5f9852ebed3163afc9fcf70fba34ac13d7c7914 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -14,6 +14,18 @@ ul.notes {
   margin: 0px;
   padding: 0px;
 
+  .timeline-icon {
+    float: left;
+  }
+
+  .timeline-content {
+    margin-left: 55px;
+  }
+
+  .note_created_ago, .note-updated-at {
+    white-space: nowrap;
+  }
+
   .system-note {
     font-size: 14px;
     padding-top: 10px;
@@ -151,6 +163,7 @@ ul.notes {
     border-left: none;
 
     &.notes_line {
+      vertical-align: middle;
       text-align: center;
       padding: 10px 0;
       background: #FFF;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 4767c65d9a7858ccb9c30b77966cd6aaacc0642a..de4d9fd80fa25c7c8f1cdc3af1b0fc30e48f7542 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -78,3 +78,39 @@
   max-width: 750px;
   margin: auto;
 }
+
+.modal-profile-crop {
+  .modal-dialog {
+    width: 500px;
+  }
+
+  .modal-body {
+    p {
+      display: table;
+      margin: auto;
+      overflow: hidden;
+    }
+
+    img {
+      display: block;
+      max-width: 400px;
+      max-height: 400px;
+    }
+
+    .cropper-bg {
+      background: none;
+    }
+
+    .cropper-crop-box {
+      box-sizing: content-box;
+      border: 999px solid transparentize(#ccc, 0.5);
+      @include transform(translate(-999px, -999px));
+    }
+  }
+}
+
+@media (max-width: 520px) {
+  .modal-profile-crop .modal-dialog {
+    width: auto;
+  }
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index b86d1a98723f8a57d346faa3e5507c8f61c00e24..247ac83c24a02fff34c799594e6b9a6c641aba71 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -397,15 +397,10 @@ pre.light-well {
 
     .project-full-name {
       @include str-truncated;
-      font-weight: 600;
-      color: #4c4e54;
     }
 
-    .project-controls {
-      float: right;
-      color: $gl-gray;
+    .controls {
       line-height: 40px;
-      color: #7f8fa4;
 
       a:hover {
         text-decoration: none;
@@ -415,16 +410,6 @@ pre.light-well {
         margin-left: 10px;
       }
     }
-
-    .project-description {
-      color: #7f8fa4;
-
-      p {
-        @include str-truncated;
-        margin-bottom: 0;
-        color: #7f8fa4;
-      }
-    }
   }
 
   .bottom {
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 1430d01859d9352d472c249a662ad4b9eed0e24c..0161642d87136a977ee1298daed388b4463f4d79 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -2,30 +2,6 @@
   padding: 2px;
 }
 
-
-.snippet-row {
-  .snippet-title {
-    font-size: 15px;
-    font-weight: bold;
-    line-height: 20px;
-    margin-bottom: 2px;
-
-    .monospace {
-      font-weight: normal;
-    }
-  }
-
-  .snippet-info {
-    color: #888;
-    font-size: 13px;
-    line-height: 24px;
-
-    a {
-      color: #888;
-    }
-  }
-}
-
 .snippet-holder {
   margin-bottom: -$gl-padding;
 
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 2f57f21963d22ebe2de6e68c7f8d3df020505782..0dc5a905f995ab35591d69a4f57a9ff935acedb7 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -12,29 +12,10 @@
   }
 }
 
-.todos {
-  .panel {
-    border-top: none;
-    margin-bottom: 0;
-  }
-}
-
 .todo-item {
   font-size: $gl-font-size;
-  padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
-  border-bottom: 1px solid $table-border-color;
-  color: #7f8fa4;
-
-  &.todo-inline {
-    .avatar {
-      position: relative;
-      top: -2px;
-    }
-
-    .todo-title {
-      line-height: 40px;
-    }
-  }
+  padding-left: $gl-avatar-size + $gl-padding-top;
+  color: $secondary-text;
 
   a {
     color: #4c4e54;
@@ -48,7 +29,7 @@
     @include str-truncated(calc(100% - 174px));
     font-weight: 600;
 
-    .author_name {
+    .author-name {
       color: #333;
     }
   }
@@ -88,17 +69,7 @@
         margin-bottom: 0;
       }
     }
-
-    .todo-note-icon {
-      color: #777;
-      float: left;
-      font-size: $gl-font-size;
-      line-height: 16px;
-      margin-right: 5px;
-    }
   }
-
-  &:last-child { border:none }
 }
 
 @media (max-width: $screen-xs-max) {
diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss
index 185f3622e6474475d96c0c46ad706b224f86b62f..05fa9312efbb845e4324b114cd9a451a1d85006a 100644
--- a/app/assets/stylesheets/pages/ui_dev_kit.scss
+++ b/app/assets/stylesheets/pages/ui_dev_kit.scss
@@ -3,4 +3,15 @@
     margin: 35px 0 20px;
     font-weight: bold;
   }
+
+  .example {
+    &:before {
+      content: "Example";
+      color: #BBB;
+    }
+
+    padding: 15px;
+    border: 1px dashed #ddd;
+    margin-bottom: 15px;
+  }
 }
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index f3bfede4354afc025e408e88b8000a96a56cc4a1..8f83fdd02bc71c256564ac40e6731415021e85ce 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -12,11 +12,13 @@ def new
 
     current_user.save! if current_user.changed?
 
-    if two_factor_grace_period_expired?
-      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 enable Two-factor Authentication for your account before #{l(grace_period_deadline)}."
+    if two_factor_authentication_required?
+      if two_factor_grace_period_expired?
+        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 enable Two-factor Authentication for your account before #{l(grace_period_deadline)}."
+      end
     end
 
     @qr_code = build_qr_code
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 28803164fcfb29d079f76598b4b75b5069fb65b7..fa7a11489615f042b94a3badac85dcfdff588aaa 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -65,6 +65,9 @@ def authorize_change_username!
 
   def user_params
     params.require(:user).permit(
+      :avatar_crop_x,
+      :avatar_crop_y,
+      :avatar_crop_size,
       :avatar,
       :bio,
       :email,
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index f7e6bb34443e1daa1ae263cdb1a6c6d4bc9f07b3..b64dbbd89ce9eb0d15f80716961c6d3b4b53e7d6 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -1,4 +1,6 @@
 class Projects::AvatarsController < Projects::ApplicationController
+  include BlobHelper
+
   before_action :project
 
   def show
@@ -7,7 +9,7 @@ def show
       headers['X-Content-Type-Options'] = 'nosniff'
       headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
       headers['Content-Disposition'] = 'inline'
-      headers['Content-Type'] = @blob.content_type
+      headers['Content-Type'] = safe_content_type(@blob)
       head :ok # 'render nothing: true' messes up the Content-Type
     else
       render_404
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 0c551501ca4cd58c4808d90850a9fc66d859240a..a0835c9aad0e114e77d26333debbd95e9523c733 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -4,12 +4,22 @@ class Projects::ForksController < Projects::ApplicationController
   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
+    base_query = project.forks.includes(:creator)
+
+    @forks = if current_user
+               base_query.where('projects.visibility_level IN (?) OR projects.id IN (?)',
+                                Project.public_and_internal_levels,
+                                current_user.authorized_projects.pluck(:id))
+             else
+               base_query.where('projects.visibility_level = ?', Project::PUBLIC)
+             end
+
+    @total_forks_count   = base_query.size
+    @private_forks_count = @total_forks_count - @forks.size
+    @public_forks_count  = @total_forks_count - @private_forks_count
+
+    @sort  = params[:sort] || 'id_desc'
+    @forks = @forks.order_by(@sort).page(params[:page]).per(PER_PAGE)
   end
 
   def new
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 87b4d08da0e75aa9421a703c892e51655e53633b..d9723acb1d96ee8982577bed7ac498533d625119 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -1,6 +1,7 @@
 # Controller for viewing a file's raw
 class Projects::RawController < Projects::ApplicationController
   include ExtractsPath
+  include BlobHelper
 
   before_action :require_non_empty_project
   before_action :assign_ref_vars
@@ -17,7 +18,7 @@ def show
       else
         headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
         headers['Content-Disposition'] = 'inline'
-        headers['Content-Type'] = get_blob_type
+        headers['Content-Type'] = safe_content_type(@blob)
         head :ok # 'render nothing: true' messes up the Content-Type
       end
     else
@@ -27,16 +28,6 @@ def show
 
   private
 
-  def get_blob_type
-    if @blob.text?
-      'text/plain; charset=utf-8'
-    elsif @blob.image?
-      @blob.content_type
-    else
-      'application/octet-stream'
-    end
-  end
-
   def send_lfs_object
     lfs_object = find_lfs_object
 
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 6055b60608622e8413d5e72874ec109da3f825a4..626213c67287a592a1fd30b221b4f7c4ca824e42 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -57,7 +57,7 @@ def contributed_projects
 
   def contributions_calendar
     @contributions_calendar ||= Gitlab::ContributionsCalendar.
-      new(contributed_projects.reject(&:forked?), @user)
+      new(contributed_projects, @user)
   end
 
   def load_events
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index d7e4d7c447ada6296a0b53ee3c025b2ecb079a4b..a46d4fe7ccffa9167335656390e2869e0c0d1dfe 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -3,7 +3,11 @@ def brand_title
     if brand_item && brand_item.title
       brand_item.title
     else
+<<<<<<< HEAD
       'GitLab Enterprise Edition'
+=======
+      'GitLab Community Edition'
+>>>>>>> 204a9895333178cc90b40ef365ad566d750fa594
     end
   end
 
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 7143a7448699fd9fafa49507b25f211cb5aaf584..7f63a2e2cb4f7938910c54efe5d89c113fec3af9 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -134,4 +134,22 @@ def sanitize_svg(blob)
     blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml
     blob
   end
+
+  # If we blindly set the 'real' content type when serving a Git blob we
+  # are enabling XSS attacks. An attacker could upload e.g. a Javascript
+  # file to a Git repository, trick the browser of a victim into
+  # downloading the blob, and then the 'application/javascript' content
+  # type would tell the browser to execute the attacker's Javascript. By
+  # overriding the content type and setting it to 'text/plain' (in the
+  # example of Javascript) we tell the browser of the victim not to
+  # execute untrusted data.
+  def safe_content_type(blob)
+    if blob.text?
+      'text/plain; charset=utf-8'
+    elsif blob.image?
+      blob.content_type
+    else
+      'application/octet-stream'
+    end
+  end
 end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 31bf45baeb708ec4cabf14b60f8e08a10740f68f..e5fcaab9551975f9381a24bb73035d7aedcc1a87 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -168,11 +168,11 @@ def event_note_title_html(event)
         link_to(namespace_project_snippet_path(event.project.namespace,
                                                event.project,
                                                event.note_target)) do
-          "#{event.note_target_type} ##{truncate event.note_target_id}"
+          "#{event.note_target_type} #{truncate event.note_target.to_reference}"
         end
       else
         link_to event_note_target_path(event) do
-          "#{event.note_target_type} ##{truncate event.note_target_iid}"
+          "#{event.note_target_type} #{truncate event.note_target.to_reference}"
         end
       end
     else
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index 40716c5ccfa9ec4412b7a7cd49ff38cddf65a2a3..5f4c5d8c9e09fd7948254a2c72b242d694b2c55d 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 # == Schema Information
 #
 # Table name: appearances
@@ -12,6 +13,8 @@
 #  header_logo  :string(255)
 #
 
+=======
+>>>>>>> 204a9895333178cc90b40ef365ad566d750fa594
 class Appearance < ActiveRecord::Base
   validates :title,       presence: true
   validates :description, presence: true
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 5e65c376e7548af8518256d13258b86460e0becc..03e5217ef4bb9edbe2a3fcd7f67fc7f4c17afcbb 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -28,7 +28,7 @@ class Milestone < ActiveRecord::Base
 
   belongs_to :project
   has_many :issues
-  has_many :labels, through: :issues
+  has_many :labels, -> { distinct.reorder('labels.title') },  through: :issues
   has_many :merge_requests
   has_many :participants, through: :issues, source: :assignee
 
diff --git a/app/models/project.rb b/app/models/project.rb
index fb347624b024cb22d5001a1f38f9dba18613518c..5d99637299f291eae9cb44467a5e820408b0f033 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -908,10 +908,7 @@ def unarchive!
   end
 
   def change_head(branch)
-    # Cached divergent commit counts are based on repository head
-    repository.expire_branch_cache
-    repository.expire_root_ref_cache
-
+    repository.before_change_head
     gitlab_shell.update_repository_head(self.path_with_namespace, branch)
     reload_default_branch
   end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 4844e81feb71f9a47699c2a01efc993cf0c316e4..bebe873cc6573a0e3f9954088ac25cd2c83f1566 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -269,15 +269,6 @@ def expire_cache(branch_name = nil)
     expire_emptiness_caches if empty?
   end
 
-  # Expires _all_ caches, including those that would normally only be expired
-  # under specific conditions.
-  def expire_all_caches!
-    expire_cache
-    expire_root_ref_cache
-    expire_emptiness_caches
-    expire_has_visible_content_cache
-  end
-
   def expire_branch_cache(branch_name = nil)
     # When we push to the root branch we have to flush the cache for all other
     # branches as their statistics are based on the commits relative to the
@@ -331,6 +322,46 @@ def expire_branch_names
     cache.expire(:branch_names)
   end
 
+  # Runs code just before a repository is deleted.
+  def before_delete
+    expire_cache if exists?
+
+    expire_root_ref_cache
+    expire_emptiness_caches
+  end
+
+  # Runs code just before the HEAD of a repository is changed.
+  def before_change_head
+    # Cached divergent commit counts are based on repository head
+    expire_branch_cache
+    expire_root_ref_cache
+  end
+
+  # Runs code before creating a new tag.
+  def before_create_tag
+    expire_cache
+  end
+
+  # Runs code after a repository has been forked/imported.
+  def after_import
+    expire_emptiness_caches
+  end
+
+  # Runs code after a new commit has been pushed.
+  def after_push_commit(branch_name)
+    expire_cache(branch_name)
+  end
+
+  # Runs code after a new branch has been created.
+  def after_create_branch
+    expire_has_visible_content_cache
+  end
+
+  # Runs code after an existing branch has been removed.
+  def after_remove_branch
+    expire_has_visible_content_cache
+  end
+
   def method_missing(m, *args, &block)
     if m == :lookup && !block_given?
       lookup_cache[m] ||= {}
diff --git a/app/models/user.rb b/app/models/user.rb
index 2772952c295f620af1ba07f67330913f99775e20..07b36f73460bc566031ee4db93a027108bcd65f1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -98,6 +98,9 @@ class User < ActiveRecord::Base
   # Virtual attribute for authenticating by either username or email
   attr_accessor :login
 
+  # Virtual attributes to define avatar cropping
+  attr_accessor :avatar_crop_x, :avatar_crop_y, :avatar_crop_size
+
   #
   # Relations
   #
@@ -165,6 +168,11 @@ class User < ActiveRecord::Base
   validate :owns_public_email, if: ->(user) { user.public_email_changed? }
   validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
 
+  validates :avatar_crop_x, :avatar_crop_y, :avatar_crop_size,
+    numericality: { only_integer: true },
+    presence: true,
+    if: ->(user) { user.avatar? }
+
   before_validation :generate_password, on: :create
   before_validation :restricted_signup_domains, on: :create
   before_validation :sanitize_attrs
@@ -383,11 +391,12 @@ def recently_sent_password_reset?
 
   def disable_two_factor!
     update_attributes(
-      two_factor_enabled:        false,
-      encrypted_otp_secret:      nil,
-      encrypted_otp_secret_iv:   nil,
-      encrypted_otp_secret_salt: nil,
-      otp_backup_codes:          nil
+      two_factor_enabled:          false,
+      encrypted_otp_secret:        nil,
+      encrypted_otp_secret_iv:     nil,
+      encrypted_otp_secret_salt:   nil,
+      otp_grace_period_started_at: nil,
+      otp_backup_codes:            nil
     )
   end
 
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 2544f82f91f06254f93f4cebf0da5d285af34021..de54712d9a6361b08803cfb21b69e4ca94206ee1 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -16,13 +16,13 @@ class GitPushService < BaseService
   #  5. Executes the project's services
   #
   def execute
-    @project.repository.expire_cache(branch_name)
+    @project.repository.after_push_commit(branch_name)
 
     if push_remove_branch?
-      @project.repository.expire_has_visible_content_cache
+      @project.repository.after_remove_branch
       @push_commits = []
     elsif push_to_new_branch?
-      @project.repository.expire_has_visible_content_cache
+      @project.repository.after_create_branch
 
       # Re-find the pushed commits.
       if is_default_branch?
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index e06650fb47a290ead834e8e3529925594faa42eb..ca0dd8eeb696b1f80a50f27c88f29b60a27d5eae 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -2,7 +2,7 @@ class GitTagPushService
   attr_accessor :project, :user, :push_data
 
   def execute(project, user, oldrev, newrev, ref, mirror_update: false)
-    project.repository.expire_cache
+    project.repository.before_create_tag
 
     @project, @user = project, user
     @push_data = build_push_data(oldrev, newrev, ref)
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index f4dcb142850452cf51b39fa53c69bb1d9a4da5e8..df5054f08d7f35c2d21449ab593cb92a5abb47c0 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -76,11 +76,9 @@ def removal_path(path)
     end
 
     def flush_caches(project, wiki_path)
-      project.repository.expire_all_caches! if project.repository.exists?
+      project.repository.before_delete
 
-      wiki_repo = Repository.new(wiki_path, project)
-
-      wiki_repo.expire_all_caches! if wiki_repo.exists?
+      Repository.new(wiki_path, project).before_delete
     end
   end
 end
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
index 6135c3ad96f4e5c3916f426d08cc177880829a2a..2c72df44ff0ba10ffb9ab4daed36a9212fe950ce 100644
--- a/app/uploaders/avatar_uploader.rb
+++ b/app/uploaders/avatar_uploader.rb
@@ -2,11 +2,22 @@
 
 class AvatarUploader < CarrierWave::Uploader::Base
   include UploaderHelper
+  include CarrierWave::MiniMagick
 
   storage :file
 
   after :store, :reset_events_cache
 
+  process :cropper
+
+  def cropper
+    return unless model.respond_to?(:avatar_crop_size) && model.valid?
+
+    manipulate! do |img|
+      img.crop "#{model.avatar_crop_size}x#{model.avatar_crop_size}+#{model.avatar_crop_x}+#{model.avatar_crop_y}"
+    end
+  end
+
   def store_dir
     "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
   end
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
index 2848b9cd33d1603fd5860315f99dbe961744ad21..a77beb2683dd88a94dfe89c04923ba37c8bb4d01 100644
--- a/app/validators/url_validator.rb
+++ b/app/validators/url_validator.rb
@@ -29,8 +29,11 @@ def default_options
   end
 
   def valid_url?(value)
+    return false if value.nil?
+
     options = default_options.merge(self.options)
 
+    value.strip!
     value =~ /\A#{URI.regexp(options[:protocols])}\z/
   end
 end
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 6975f6ed0db9ce2268fd09fd4253ecd1c041b728..f878d36e7394008d913e1d7e467bd38a36049d93 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -1,11 +1,11 @@
 %li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo) }
-  .todo-item{class: 'todo-block'}
+  .todo-item.todo-block
     = image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
 
     .todo-title
-      %span.author_name
+      %span.author-name
         = link_to_author todo
-      %span.todo_label
+      %span.todo-label
         = todo_action_name(todo)
         = todo_target_link(todo)
 
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index 4ecf1c33d2ad5e4c37e73bd21e2f57e75d64c31b..e9e16a7646f4abf54a28e37b954b661b182bc4d6 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -4,7 +4,7 @@
     = event_action_name(event)
 
   - if event.target
-    %strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target]
+    %strong= link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target]
 
   = event_preposition(event)
 
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 746386cab5807683353eaa118548e7abd27db84b..a2c0a858930b8bfa979640efc701bc6cdcf95cfa 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -31,64 +31,91 @@
 
   %h2#blocks Blocks
 
-  %h4
+  .lead
+    Content block separated with botton border
+    %code .content-block
+
+  .example
+    .content-block
+      %h4 Normal block inside content
+      = lorem
+
+    .content-block
+      %h4 Second block
+      = lorem
+
+  .lead
+    Gray content block with side padding using
     %code .gray-content-block
 
-  .gray-content-block.middle-block
-    %h4 Normal block inside content
-    = lorem
+  .example
+    .gray-content-block
+      %h4 Normal block inside content
+      = lorem
 
-  .gray-content-block.second-block
-    %h4 Second block
-    = lorem
+    .gray-content-block.second-block
+      %h4 Second block
+      = lorem
 
 
-  %h4
+  .lead
+    Cover block for profile page with avatar, name and description
     %code .cover-block
-  %br
-  .cover-block
-    .avatar-holder
-      = image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: ''
-    .cover-title
-      John Smith
-
-    .cover-desc
-      = lorem
+  .example
+    .cover-block
+      .avatar-holder
+        = image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: ''
+      .cover-title
+        John Smith
+
+      .cover-desc
+        = lorem
 
-    .cover-controls
-      = link_to '#', class: 'btn btn-gray' do
-        = icon('pencil')
-      &nbsp;
-      = link_to '#', class: 'btn btn-gray' do
-        = icon('rss')
+      .cover-controls
+        = link_to '#', class: 'btn btn-gray' do
+          = icon('pencil')
+        &nbsp;
+        = link_to '#', class: 'btn btn-gray' do
+          = icon('rss')
 
   %h2#lists Lists
 
-  %h4
+  .lead
+    Simple list using
     %code .content-list
-  %ul.content-list
-    %li
-      One item
-    %li
-      One item
-    %li
-      One item
 
-  %h4
-    %code .well-list
-  %ul.well-list
-    %li
-      One item
-    %li
-      One item
-    %li
-      One item
+  .example
+    %ul.content-list
+      %li
+        One item
+      %li
+        One item
+      %li
+        One item
 
-  %h4
-    %code .panel .well-list
+  .lead
+    List with avatar, title and description using
+    %code .content-list
+
+  .example
+    %ul.content-list
+      %li
+        = image_tag 'no_avatar.png', class: 'avatar s40'
+        .title Title
+        .description Description
+      %li
+        = image_tag 'no_avatar.png', class: 'avatar s40'
+        .title Title
+        .description Description
+      %li
+        = image_tag 'no_avatar.png', class: 'avatar s40'
+        .title Title
+        .description Description
 
-  .panel.panel-default
-    .panel-heading Your list
+  .lead
+    List with hover effect
+    %code .well-list
+  .example
     %ul.well-list
       %li
         One item
@@ -97,17 +124,18 @@
       %li
         One item
 
-  %h4
-    %code .bordered-list
-  %ul.bordered-list
-    %li
-      One item
-    %li
-      One item
-    %li
-      One item
-
-
+  .lead
+    List inside panel
+  .example
+    .panel.panel-default
+      .panel-heading Your list
+      %ul.well-list
+        %li
+          One item
+        %li
+          One item
+        %li
+          One item
 
   %h2#tables Tables
 
@@ -138,9 +166,9 @@
 
   %h2#navs Navigation
 
-  %h4
+  .lead
+    Holder for top page navigation. Includes navigation, search field, sorting and button
     %code .top-area
-  %p Holder for top page navigation. Includes navigation, search field, sorting and button
 
   .example
     .top-area
@@ -161,9 +189,9 @@
 
         = link_to 'New issue', '#', class: 'btn btn-new'
 
-  %h4
+  .lead
+    Only nav links without button and search
     %code .nav-links
-  %p Only nav links without button and search
   .example
     %ul.nav-links
       %li.active
@@ -228,43 +256,47 @@
 
   %h2#forms Forms
 
-  %h4
+  .lead
+    Horizontal form when label rendered inline with input
     %code form.horizontal-form
 
-  %form.form-horizontal
-    .form-group
-      %label.col-sm-2.control-label{:for => "inputEmail3"} Email
-      .col-sm-10
-        %input#inputEmail3.form-control{:placeholder => "Email", :type => "email"}/
-    .form-group
-      %label.col-sm-2.control-label{:for => "inputPassword3"} Password
-      .col-sm-10
-        %input#inputPassword3.form-control{:placeholder => "Password", :type => "password"}/
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        .checkbox
-          %label
-            %input{:type => "checkbox"}/
-            Remember me
-    .form-group
-      .col-sm-offset-2.col-sm-10
-        %button.btn.btn-default{:type => "submit"} Sign in
-
-  %h4
+  .example
+    %form.form-horizontal
+      .form-group
+        %label.col-sm-2.control-label{:for => "inputEmail3"} Email
+        .col-sm-10
+          %input#inputEmail3.form-control{:placeholder => "Email", :type => "email"}/
+      .form-group
+        %label.col-sm-2.control-label{:for => "inputPassword3"} Password
+        .col-sm-10
+          %input#inputPassword3.form-control{:placeholder => "Password", :type => "password"}/
+      .form-group
+        .col-sm-offset-2.col-sm-10
+          .checkbox
+            %label
+              %input{:type => "checkbox"}/
+              Remember me
+      .form-group
+        .col-sm-offset-2.col-sm-10
+          %button.btn.btn-default{:type => "submit"} Sign in
+
+  .lead
+    Form when label rendered above input
     %code form
 
-  %form
-    .form-group
-      %label{:for => "exampleInputEmail1"} Email address
-      %input#exampleInputEmail1.form-control{:placeholder => "Enter email", :type => "email"}/
-    .form-group
-      %label{:for => "exampleInputPassword1"} Password
-      %input#exampleInputPassword1.form-control{:placeholder => "Password", :type => "password"}/
-    .checkbox
-      %label
-        %input{:type => "checkbox"}/
-        Remember me
-    %button.btn.btn-default{:type => "submit"} Sign in
+  .example
+    %form
+      .form-group
+        %label{:for => "exampleInputEmail1"} Email address
+        %input#exampleInputEmail1.form-control{:placeholder => "Enter email", :type => "email"}/
+      .form-group
+        %label{:for => "exampleInputPassword1"} Password
+        %input#exampleInputPassword1.form-control{:placeholder => "Password", :type => "password"}/
+      .checkbox
+        %label
+          %input{:type => "checkbox"}/
+          Remember me
+      %button.btn.btn-default{:type => "submit"} Sign in
 
   %h2#file File
   %h4
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 5051c6bf83b2013b5f49551e443f5f095c1c6604..64c4bdceff994dee56c661d6768ec68c55983e3f 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -90,6 +90,9 @@
             &nbsp;
             %span.file_name.js-avatar-filename File name...
             = f.file_field :avatar, class: "js-user-avatar-input hidden"
+            = f.hidden_field :avatar_crop_x
+            = f.hidden_field :avatar_crop_y
+            = f.hidden_field :avatar_crop_size
             .light The maximum file size allowed is 200KB.
             - if @user.avatar?
               %hr
@@ -99,3 +102,19 @@
   .form-actions
     = f.submit 'Save changes', class: "btn btn-success"
     = link_to "Cancel", user_path(current_user), class: "btn btn-cancel"
+
+.modal.modal-profile-crop
+  .modal-dialog
+    .modal-content
+      .modal-header
+        %button.close{type: 'button', data: {dismiss: 'modal'}}
+          %span
+            &times;
+        %h4.modal-title
+          Crop your new profile picture
+      .modal-body
+        %p
+          %img.modal-profile-crop-image
+      .modal-footer
+        %button.btn.btn-primary.js-upload-user-avatar{:type => "button"}
+          Set new profile picture
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index f99bc9a85eb7aca109f57e670b68358021bac3b7..63ede71e6f1bdc5a17412abe711f8faf2f40537c 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -3,17 +3,16 @@
     Too many changes to show.
     .pull-right
       - unless diff_hard_limit_enabled?
-        = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm btn-warning"
+        = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm"
 
       - if current_controller?(:commit) or current_controller?(:merge_requests)
         - if current_controller?(:commit)
-          = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-warning btn-sm"
-          = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-warning btn-sm"
+          = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm"
+          = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-sm"
         - elsif @merge_request && @merge_request.persisted?
-          = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-warning btn-sm"
-          = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-warning btn-sm"
+          = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-sm"
+          = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
   %p
     To preserve performance only
     %strong #{shown_files_count} of #{diffs.size}
     files are displayed.
-
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index 42fa6fdb782709d43fef3eaf7cba0eeb4d601018..ace22625d1d748e3b8d521593900d74d4c5d7ed8 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -1,9 +1,7 @@
 .top-area
   .nav-text
-    - 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}
+    - full_count_title = "#{@public_forks_count} public and #{@private_forks_count} private"
+    == #{pluralize(@total_forks_count, 'fork')}: #{full_count_title}
 
   .nav-controls
     = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter project-filter-form-field form-control input-short',
@@ -41,17 +39,17 @@
 
 
 .projects-list-holder
-  - if @public_forks.blank?
+  - if @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,
+    = render 'shared/projects/list', projects: @forks, use_creator_avatar: true,
       forks: true, show_last_commit_as_description: true
 
-    - if protected_count > 0
+    - if @private_forks_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')
+          %strong= pluralize(@private_forks_count, 'private fork')
           %span you have no access to.
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 81a6b93c7d39fd71da17eccf8c2f2291f0e7b275..0daf492b652c0820b2d0d67df2e9cfc6be70ed28 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -5,7 +5,7 @@
 
   .issue-title
     %span.issue-title-text
-      = link_to_gfm issue.title, issue_path(issue), class: "row_title"
+      = link_to_gfm issue.title, issue_path(issue), class: "title"
     %ul.controls.light
       - if issue.closed?
         %li
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index b55f6a2d32ad3b5c88c522d37353835117be15cf..18cf3f14f0bf34e5c12b714561a049f1aa4f26d1 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,7 +1,7 @@
 %li{ class: mr_css_classes(merge_request) }
   .merge-request-title
     %span.merge-request-title-text
-      = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
+      = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "title"
     %ul.controls.light
       - if merge_request.merged?
         %li
@@ -48,7 +48,7 @@
             = note_count
 
   .merge-request-info
-    \##{merge_request.iid} &middot;
+    #{merge_request.to_reference} &middot;
     opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
     by #{link_to_member(@project, merge_request.author, avatar: false)}
     - if merge_request.target_project.default_branch != merge_request.target_branch
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 648512e5379200801189d0cf81cc5e4ff42b3283..d7bc26e24b9d15b08c84279c006fddf0e1d1e880 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,4 +1,4 @@
-- page_title           "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+- page_title           "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
 - page_description     @merge_request.description
 - page_card_attributes @merge_request.card_attributes
 
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 14ea7b17786f9e21766781f7580929b898ebb6f9..b634a4af8d2c65f17328ccf2e1954ad858909b1c 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -2,7 +2,7 @@
   .status-box{ class: status_box_class(@merge_request) }
     = @merge_request.state_human_name
   %span.identifier
-    Merge Request ##{@merge_request.iid}
+    Merge Request #{@merge_request.to_reference}
   %span.creator
     &middot;
     by #{link_to_member(@project, @merge_request.author, size: 24)}
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index e858c412836075137a5469299b6a577b70c2350e..52972576aff1f25ecac81da658998e297fda1195 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -28,7 +28,7 @@
           %a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'}
             = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
           - if note.updated_at != note.created_at
-            %span
+            %span.note-updated-at
               &middot;
               = icon('edit', title: 'edited')
               = time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago')
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 4b4c9e9eabe20248a893f855cbada00e0bb11327..8ff9d4c1c7f7e620e87fe826cf50c0cd6c69bbc1 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -8,7 +8,7 @@
           .pull-right
             = link_to 'New issue', new_namespace_project_issue_path(project.namespace, project)
 
-      %ul.well-list.issues-list
+      %ul.content-list.issues-list
         - group[1].each do |issue|
           = render 'projects/issues/issue', issue: issue
   = paginate @issues, theme: "gitlab"
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index be17a511b26c7ec0fef762d433d8667115b99d16..e74fc36c797c0ba6c0078e5658e18e689f8898be 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -8,7 +8,7 @@
           .pull-right
             = link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project)
 
-      %ul.well-list.mr-list
+      %ul.content-list.mr-list
         - group[1].each do |merge_request|
           = render 'projects/merge_requests/merge_request', merge_request: merge_request
   = paginate @merge_requests, theme: "gitlab"
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index 289b0bfe1eb18e844d9800cdb7c75080439aef00..fb9a8db08893299c53db296e76f9e586fea3a009 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -22,13 +22,13 @@
       = number_with_delimiter(group.users.count)
 
   = image_tag group_icon(group), class: "avatar s40 hidden-xs"
-  = link_to group, class: 'group-name' do
-    %span.item-title= group.name
+  = link_to group, class: 'group-name title' do
+    = group.name
 
   - if group_member
     as
     %span #{group_member.human_access}
 
   - if group.description.present?
-    .light
+    .description
       = markdown(group.description, pipeline: :description)
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index cf93f469aa2d1b9e4d3da5e898d4f11788b28c7d..b25c477525f69e10cf38b675337ef65ddd452636 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -1,14 +1,22 @@
 %aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
   .issuable-sidebar
     .block
+<<<<<<< HEAD
       %span.issuable-count.pull-left.hide-collapsed
+=======
+      %span.issuable-count.hide-collapsed.pull-left
+>>>>>>> 204a9895333178cc90b40ef365ad566d750fa594
         = issuable.iid
         of
         = issuables_count(issuable)
       %span.pull-right
         %a.gutter-toggle{href: '#'}
           = sidebar_gutter_toggle_icon
+<<<<<<< HEAD
       .issuable-nav.pull-right.btn-group.hide-collapsed{role: 'group', "aria-label" => '...'}
+=======
+      .issuable-nav.hide-collapsed.pull-right.btn-group{role: 'group', "aria-label" => '...'}
+>>>>>>> 204a9895333178cc90b40ef365ad566d750fa594
         - if prev_issuable = prev_issuable_for(issuable)
           = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn'
         - else
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 75684b972f1c627632166e6c5567760e521db6a5..e75af50a53765a9cdb60150bf5ab930f81af2931 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -7,7 +7,7 @@
 - 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
+%ul.projects-list.content-list
   - if projects.any?
     - projects.each_with_index do |project, i|
       - css_class = (i >= projects_limit) ? 'hide' : nil
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 72061e272f166529c427561a327d08b56af13e76..99e48e86e389ae7ef5f8451aa0b4e7aa5ef2a2b5 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -19,7 +19,7 @@
             = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
           - else
             = project_icon(project, alt: '', class: 'avatar project-avatar s40')
-      %span.project-full-name
+      %span.project-full-name.title
         %span.namespace-name
           - if project.namespace && !skip_namespace
             = project.namespace.human_name
@@ -27,7 +27,7 @@
         %span.project-name.filter-title
           = project.name
 
-    .project-controls
+    .controls
       - if ci_commit
         %span
           = render_ci_status(ci_commit)
@@ -43,9 +43,9 @@
         title: "#{visibility_level_label(project.visibility_level)} - #{project_visibility_level_description(project.visibility_level)}"}
         = visibility_level_icon(project.visibility_level, fw: false)
     - if show_last_commit_as_description
-      .project-description
+      .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
+      .description
         = markdown(project.description, pipeline: :description)
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index c6294caddc7f7d26d8d9fc2d002f5a3d3e9b38bd..a316a085107e2edc6f0f7b4ef7b075ce5b16b313 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -1,10 +1,12 @@
 %li.snippet-row
+  = image_tag avatar_icon(snippet.author_email), class: "avatar s40 hidden-xs", alt: ''
+
   .snippet-title
-    = link_to reliable_snippet_path(snippet) do
+    = link_to reliable_snippet_path(snippet), class: 'title' do
       = truncate(snippet.title, length: 60)
       - if snippet.private?
         %span.label.label-gray
-          %i.fa.fa-lock
+          = icon('lock')
           private
     %span.monospace.pull-right
       = snippet.file_name
@@ -15,6 +17,5 @@
 
   .snippet-info
     = link_to user_snippets_path(snippet.author) do
-      = image_tag avatar_icon(snippet.author_email), class: "avatar s24", alt: ''
       = snippet.author_name
     authored #{time_ago_with_tooltip(snippet.created_at)}
diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
index d9aa4dd1d2eb22db73e91a8f8e98cad288474197..80a3e731e1df9e3c38b09180d8665214d0856565 100644
--- a/app/views/snippets/_snippets.html.haml
+++ b/app/views/snippets/_snippets.html.haml
@@ -1,4 +1,4 @@
-%ul.bordered-list
+%ul.content-list
   = render partial: 'shared/snippets/snippet', collection: @snippets
   - if @snippets.empty?
     %li
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index 2572b9d6d983a0d840990cb23d05fceee7b67fb0..21d311579e34af22f636a9c3f2d35e05cd4bd850 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -27,7 +27,7 @@ def perform(project_id, source_path, target_path)
       return
     end
 
-    project.repository.expire_emptiness_caches
+    project.repository.after_import
     project.import_finish
   end
 end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index da4ae186dc473526d8da44e5abafb1f96f04271d..c36ec31eef29b3a321fdf4e733c3232ac7d61886 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -18,7 +18,7 @@ def perform(project_id)
       return
     end
 
-    project.repository.expire_emptiness_caches
+    project.repository.after_import
     project.import_finish
 
     # Explicitly update mirror so that upstream remote is created and fetched
diff --git a/doc/README.md b/doc/README.md
index 2c0d745fb3fdfffe3782e795aa7876e2385325d6..d8f5a80236f09a37be10dc78ca9dd6d38b0936a6 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -19,40 +19,42 @@
 - [GitLab Pages](pages/README.md) Using GitLab Pages.
 - [Custom templates for issues and merge requests](customization/issue_and_merge_request_template.md) Pre-fill the description of issues and merge requests to your liking.
 
-## CI Documentation
+## CI User documentation
 
-- [Quick Start](ci/quick_start/README.md)
-- [Enable or disable GitLab CI](ci/enable_or_disable_ci.md)
-- [Configuring project (.gitlab-ci.yml)](ci/yaml/README.md)
-- [Configuring runner](ci/runners/README.md)
-- [Configuring deployment](ci/deployment/README.md)
-- [Using Docker Images](ci/docker/using_docker_images.md)
-- [Using Docker Build](ci/docker/using_docker_build.md)
-- [Using Variables](ci/variables/README.md)
-- [Using SSH keys](ci/ssh_keys/README.md)
+- [Get started with GitLab CI](ci/quick_start/README.md)
+- [Learn how to enable or disable GitLab CI](ci/enable_or_disable_ci.md)
+- [Learn how `.gitlab-ci.yml` works](ci/yaml/README.md)
+- [Configure a Runner, the application that runs your builds](ci/runners/README.md)
+- [Use Docker images with GitLab Runner](ci/docker/using_docker_images.md)
+- [Use CI to build Docker images](ci/docker/using_docker_build.md)
+- [Use variables in your `.gitlab-ci.yml`](ci/variables/README.md)
+- [Use SSH keys in your build environment](ci/ssh_keys/README.md)
+- [Trigger builds through the API](ci/triggers/README.md)
+- [Build artifacts](ci/build_artifacts/README.md)
 - [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
+### CI Examples
 
-- [Testing PHP](ci/languages/php.md)
+- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
+- [Test your PHP applications](ci/examples/php.md)
+- [Test and deploy Ruby applications to Heroku](ci/examples/test-and-deploy-ruby-application-to-heroku.md)
+- [Test and deploy Python applications to Heroku](ci/examples/test-and-deploy-python-application-to-heroku.md)
+- [Test Clojure applications](ci/examples/test-clojure-application.md)
+- [Using `dpl` as deployment tool](ci/deployment/README.md)
+- Help your favorite programming language and GitLab by sending a merge request
+  with a guide for that language.
 
 ### CI Services
 
+GitLab CI uses the `services` keyword to define what docker containers should
+be linked with your base image. Below is a list of examples you may use:
+
 - [Using MySQL](ci/services/mysql.md)
 - [Using PostgreSQL](ci/services/postgres.md)
 - [Using Redis](ci/services/redis.md)
 - [Using Other Services](ci/docker/using_docker_images.md#how-to-use-other-images-as-services)
 
-### CI Examples
-
-- [Test and deploy Ruby applications to Heroku](ci/examples/test-and-deploy-ruby-application-to-heroku.md)
-- [Test and deploy Python applications to Heroku](ci/examples/test-and-deploy-python-application-to-heroku.md)
-- [Test Clojure applications](ci/examples/test-clojure-application.md)
-- Help your favorite programming language and GitLab by sending a merge request with a guide for that language.
-
 ## Administrator documentation
 
 - [Audit Events](administration/audit_events.md) Check how user access changed in projects and groups.
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 5886829be51084594c0637561b464f89d2866e3c..2120b5b28509a53a089d4074f1fab363fdf0e909 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -1,39 +1,37 @@
 ## GitLab CI Documentation
 
-### User documentation
-
-* [Quick Start](quick_start/README.md)
-* [Enable or disable GitLab CI](enable_or_disable_ci.md)
-* [Configuring project (.gitlab-ci.yml)](yaml/README.md)
-* [Configuring runner](runners/README.md)
-* [Configuring deployment](deployment/README.md)
-* [Using Docker Images](docker/using_docker_images.md)
-* [Using Docker Build](docker/using_docker_build.md)
-* [Using Variables](variables/README.md)
-* [Using SSH keys](ssh_keys/README.md)
-* [Triggering builds through the API](triggers/README.md)
-* [Build artifacts](build_artifacts/README.md)
-
-### Languages
-
-* [Testing PHP](languages/php.md)
-
-### Services
-
-* [Using MySQL](services/mysql.md)
-* [Using PostgreSQL](services/postgres.md)
-* [Using Redis](services/redis.md)
-* [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services)
-
-### Examples
-
-+ [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
-+ [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md)
-+ [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md)
-+ [Test Clojure applications](examples/test-clojure-application.md)
-+ Help your favorite programming language and GitLab by sending a merge request with a guide for that language.
-
-### Administrator documentation
-
-* [User permissions](permissions/README.md)
-* [API](api/README.md)
+### CI User documentation
+
+- [Get started with GitLab CI](quick_start/README.md)
+- [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md)
+- [Learn how `.gitlab-ci.yml` works](yaml/README.md)
+- [Configure a Runner, the application that runs your builds](runners/README.md)
+- [Use Docker images with GitLab Runner](docker/using_docker_images.md)
+- [Use CI to build Docker images](docker/using_docker_build.md)
+- [Use variables in your `.gitlab-ci.yml`](variables/README.md)
+- [Use SSH keys in your build environment](ssh_keys/README.md)
+- [Trigger builds through the API](triggers/README.md)
+- [Build artifacts](build_artifacts/README.md)
+- [User permissions](permissions/README.md)
+- [API](api/README.md)
+
+### CI Examples
+
+- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
+- [Test your PHP applications](examples/php.md)
+- [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md)
+- [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md)
+- [Test Clojure applications](examples/test-clojure-application.md)
+- [Using `dpl` as deployment tool](deployment/README.md)
+- Help your favorite programming language and GitLab by sending a merge request
+  with a guide for that language.
+
+### CI Services
+
+GitLab CI uses the `services` keyword to define what docker containers should
+be linked with your base image. Below is a list of examples you may use:
+
+- [Using MySQL](services/mysql.md)
+- [Using PostgreSQL](services/postgres.md)
+- [Using Redis](services/redis.md)
+- [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services)
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 1cf41aea391f32c4af004c5aed31f2456b486b7c..31f29f4a082e92ab05db675b040e4532a644c313 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -1,5 +1,13 @@
-# Build script examples
+## Build script examples
 
-+ [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
-+ [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
-+ [Test a Clojure application](test-clojure-application.md)
+- [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
+- [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
+- [Test a Clojure application](test-clojure-application.md)
+
+## Languages
+
+This is a list of languages you can test with GitLab CI. Each section has
+comprehensive documentation and comes with a test repository hosted on
+GitLab.com.
+
+- [Testing PHP](php.md)
diff --git a/doc/ci/languages/php.md b/doc/ci/examples/php.md
similarity index 100%
rename from doc/ci/languages/php.md
rename to doc/ci/examples/php.md
diff --git a/doc/ci/languages/README.md b/doc/ci/languages/README.md
deleted file mode 100644
index 54b2343e08b4a91b6465451e7ad4511bf9ef2164..0000000000000000000000000000000000000000
--- a/doc/ci/languages/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-### Languages
-
-This is a list of languages you can test with GitLab CI. Each section has
-comprehensive documentation and comes with a test repository hosted on
-GitLab.com
-
-+ [Testing PHP](php.md)
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 327c83bef720e44a034231dfc0045449aa6ea5ff..5af7be5581e72c28451b9ae248cef71e31bac596 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -201,6 +201,11 @@ You can access a builds badge image using following link:
 http://example.gitlab.com/namespace/project/badges/branch/build.svg
 ```
 
+## Examples
+
+Visit the [examples README][examples] to see a list of examples using GitLab
+CI with various languages.
+
 ## Next steps
 
 Awesome! You started using CI in GitLab!
@@ -212,3 +217,4 @@ Visit our various languages examples at <https://gitlab.com/groups/gitlab-exampl
 
 [runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation
 [blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
+[examples]: ../examples/README.md
diff --git a/doc/ci/services/README.md b/doc/ci/services/README.md
index 1ebb0a4a25085cef1fb65afb87a19d7a379daa27..4b79461d55c64d338fbdcbb66d5df0ad8fd03afd 100644
--- a/doc/ci/services/README.md
+++ b/doc/ci/services/README.md
@@ -1,9 +1,9 @@
 ## GitLab CI Services
 
-GitLab CI uses the `services` keyword to define what docker containers should be
-linked with your base image. Below is a list of examples you may use.
+GitLab CI uses the `services` keyword to define what docker containers should
+be linked with your base image. Below is a list of examples you may use.
 
-+ [Using MySQL](mysql.md)
-+ [Using PostgreSQL](postgres.md)
-+ [Using Redis](redis.md)
-+ [Using Other Services](../docker/using_docker_images.md#how-to-use-other-images-as-services)
+- [Using MySQL](mysql.md)
+- [Using PostgreSQL](postgres.md)
+- [Using Redis](redis.md)
+- [Using Other Services](../docker/using_docker_images.md#how-to-use-other-images-as-services)
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 9e89e6e395ecb33d933d45accd9aafb23382ac1f..b0e53cbc2615817cd405d8fd04401751fb088bf5 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -30,7 +30,7 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
 | **CI_BUILD_REF_NAME**   | all | The branch or tag name for which project is built |
 | **CI_BUILD_ID**         | all | The unique id of the current build that GitLab CI uses internally |
 | **CI_BUILD_REPO**       | all | The URL to clone the Git repository |
-| **CI_BUILD_TRIGGERED**  | 0.5 | The flag to indicate that build was triggered |
+| **CI_BUILD_TRIGGERED**  | 0.5 | The flag to indicate that build was [triggered] |
 | **CI_PROJECT_ID**       | all | The unique id of the current project that GitLab CI uses internally |
 | **CI_PROJECT_DIR**      | all | The full path where the repository is cloned and where the build is ran |
 
@@ -104,3 +104,5 @@ job_name:
   script:
     - export
 ```
+
+[triggered]: ../triggers/README.md
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 0edb56dc20e5c78a61374908ef75466a4ee2d1c1..051eaa04152a478481b58ef11cae322e05ab1114 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -518,3 +518,10 @@ You can find the link under `/ci/lint` of your gitlab instance.
 
 If your commit message contains `[ci skip]`, the commit will be created but the
 builds will be skipped.
+
+## Examples
+
+Visit the [examples README][examples] to see a list of examples using GitLab
+CI with various languages.
+
+[examples]: ../examples/README.md
diff --git a/doc/customization/branded_login_page.md b/doc/customization/branded_login_page.md
index ea38659059616a3b459fe2a773d082bc13a38e53..45fccc9ad878d0bb7dd35b85d5d2bbc128315b26 100644
--- a/doc/customization/branded_login_page.md
+++ b/doc/customization/branded_login_page.md
@@ -1,6 +1,10 @@
 # Changing the appearance of the login page
 
+<<<<<<< HEAD
 GitLab Enterprise Edition offers a way to put your company's identity on the login page of your GitLab server and make it a branded login page.
+=======
+GitLab Community Edition offers a way to put your company's identity on the login page of your GitLab server and make it a branded login page.
+>>>>>>> 204a9895333178cc90b40ef365ad566d750fa594
 
 By default, the page shows the GitLab logo and description.
 
diff --git a/doc/development/README.md b/doc/development/README.md
index d5bf166ad32065eb49d5f89c7089758773fd1703..b9a0d81e5ba9c3ae30d6d0f50183caefd8afae54 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -1,11 +1,12 @@
 # Development
 
 - [Architecture](architecture.md) of GitLab
-- [Shell commands](shell_commands.md) in the GitLab codebase
-- [Rake tasks](rake_tasks.md) for development
+- [Benchmarking](benchmarking.md)
 - [CI setup](ci_setup.md) for testing GitLab
+- [Gotchas](gotchas.md) to avoid
+- [How to dump production data to staging](db_dump.md)
+- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
+- [Rake tasks](rake_tasks.md) for development
+- [Shell commands](shell_commands.md) in the GitLab codebase
 - [Sidekiq debugging](sidekiq_debugging.md)
 - [UI guide](ui_guide.md) for building GitLab with existing css styles and elements
-- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
-- [How to dump production data to staging](dump_db.md)
-- [Benchmarking](benchmarking.md)
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
new file mode 100644
index 0000000000000000000000000000000000000000..21078c8d6f9aba69873a757e449d5658a1bf327c
--- /dev/null
+++ b/doc/development/gotchas.md
@@ -0,0 +1,103 @@
+# Gotchas
+
+The purpose of this guide is to document potential "gotchas" that contributors
+might encounter or should avoid during development of GitLab CE and EE.
+
+## Don't `describe` symbols
+
+Consider the following model spec:
+
+```ruby
+require 'rails_helper'
+
+describe User do
+  describe :to_param do
+    it 'converts the username to a param' do
+      user = described_class.new(username: 'John Smith')
+
+      expect(user.to_param).to eq 'john-smith'
+    end
+  end
+end
+```
+
+When run, this spec doesn't do what we might expect:
+
+```sh
+spec/models/user_spec.rb|6 error|  Failure/Error: u = described_class.new NoMethodError: undefined method `new' for :to_param:Symbol
+```
+
+### Solution
+
+Except for the top-level `describe` block, always provide a String argument to
+`describe`.
+
+## Don't `rescue Exception`
+
+See ["Why is it bad style to `rescue Exception => e` in Ruby?"][Exception].
+
+_**Note:** This rule is [enforced automatically by
+Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L911-914)._
+
+[Exception]: http://stackoverflow.com/q/10048173/223897
+
+## Don't use inline CoffeeScript in views
+
+Using the inline `:coffee` or `:coffeescript` Haml filters comes with a
+performance overhead.
+
+_**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-5-stable/config/initializers/haml.rb)
+in an initializer._
+
+### Further reading
+
+- Pull Request: [Replace CoffeeScript block into JavaScript in Views](https://git.io/vztMu)
+- Stack Overflow: [Performance implications of using :coffescript filter inside HAML templates?](http://stackoverflow.com/a/17571242/223897)
+
+## ID-based CSS selectors need to be a bit more specific
+
+Normally, because HTML `id` attributes need to be unique to the page, it's
+perfectly fine to write some JavaScript like the following:
+
+```javascript
+$('#js-my-selector').hide();
+```
+
+However, there's a feature of GitLab's Markdown processing that [automatically
+adds anchors to header elements][ToC Processing], with the `id` attribute being
+automatically generated based on the content of the header.
+
+Unfortunately, this feature makes it possible for user-generated content to
+create a header element with the same `id` attribute we're using in our
+selector, potentially breaking the JavaScript behavior. A user could break the
+above example with the following Markdown:
+
+```markdown
+## JS My Selector
+```
+
+Which gets converted to the following HTML:
+
+```html
+<h2>
+  <a id="js-my-selector" class="anchor" href="#js-my-selector" aria-hidden="true"></a>
+  JS My Selector
+</h2>
+```
+
+[ToC Processing]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/lib/banzai/filter/table_of_contents_filter.rb#L31-37
+
+### Solution
+
+The current recommended fix for this is to make our selectors slightly more
+specific:
+
+```javascript
+$('div#js-my-selector').hide();
+```
+
+### Further reading
+
+- Issue: [Merge request ToC anchor conflicts with tabs](https://gitlab.com/gitlab-org/gitlab-ce/issues/3908)
+- Merge Request: [Make tab target selectors less naive](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2023)
+- Merge Request: [Make cross-project reference's clipboard target less naive](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2024)
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 4cd5a39d8c4eb698f9bcc0c5b0f2d4a49d9415bc..398439c782d82b95166291836115f6f3163ca130 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -6,7 +6,7 @@ If a user is both in a project group and in the project itself, the highest perm
 
 If a user is a GitLab administrator they receive all permissions.
 
-On public projects the Guest role is not enforced.
+On public and internal projects the Guest role is not enforced.
 All users will be able to create issues, leave comments, and pull or download the project code.
 
 To add or import a user, you can follow the [project users and members
diff --git a/features/project/milestone.feature b/features/project/milestone.feature
index e0f4c0e9d7cb403cd4f69aa83cd59fe660109ea5..713f0f3b97965e8651ec63fefbc61a18998a1806 100644
--- a/features/project/milestone.feature
+++ b/features/project/milestone.feature
@@ -13,6 +13,7 @@ Feature: Project Milestone
     Given I visit project "Shop" milestones page
     And I click link "v2.2"
     Then I should see the labels "bug", "enhancement" and "feature"
+    And I should see the "bug" label listed only once
 
   @javascript
   Scenario: Listing labels from labels tab
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 6b0c1049ece560d1ea9512af641a487609a989dc..7895f643d0c3dd5698ca66ef1781a330074a34b9 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -27,9 +27,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
   end
 
   step 'I change my avatar' do
-    attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
-    click_button "Save changes"
-    @user.reload
+    attach_avatar
   end
 
   step 'I should see new avatar' do
@@ -42,9 +40,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
   end
 
   step 'I have an avatar' do
-    attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
-    click_button "Save changes"
-    @user.reload
+    attach_avatar
   end
 
   step 'I remove my avatar' do
@@ -233,4 +229,16 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
   step "I see that application is removed" do
     expect(page.find(".oauth-applications")).not_to have_content "test_changed"
   end
+
+  def attach_avatar
+    attach_file :user_avatar, Rails.root.join(*%w(spec fixtures banana_sample.gif))
+
+    page.find('#user_avatar_crop_x',    visible: false).set('0')
+    page.find('#user_avatar_crop_y',    visible: false).set('0')
+    page.find('#user_avatar_crop_size', visible: false).set('256')
+
+    click_button "Save changes"
+
+    @user.reload
+  end
 end
diff --git a/features/steps/project/project_milestone.rb b/features/steps/project/project_milestone.rb
index ec881c0d8fc95b416783e03819f9de920c0c2b57..2508c09e36d8612c6ca6f4e0f680a5bca5c1847b 100644
--- a/features/steps/project/project_milestone.rb
+++ b/features/steps/project/project_milestone.rb
@@ -41,6 +41,12 @@ class Spinach::Features::ProjectMilestone < Spinach::FeatureSteps
     end
   end
 
+  step 'I should see the "bug" label listed only once' do
+    page.within('#tab-labels') do
+      expect(page).to have_content('bug', count: 1)
+    end
+  end
+
   step 'I click link "v2.2"' do
     click_link "v2.2"
   end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index e38736fc28b65e1712ad29d8b4176516853d443f..2200208b946528a9bec8bfde4dfd276a3b437fc9 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -14,6 +14,14 @@ class Internal < Grape::API
       #   ref - branch name
       #   forced_push - forced_push
       #
+
+      helpers do
+        def wiki?
+          @wiki ||= params[:project].end_with?('.wiki') &&
+            !Project.find_with_namespace(params[:project])
+        end
+      end
+
       post "/allowed" do
         status 200
 
@@ -30,13 +38,12 @@ class Internal < Grape::API
         # Strip out the .wiki from the pathname before finding the
         # project. This applies the correct project permissions to
         # the wiki repository as well.
-        wiki = project_path.end_with?('.wiki')
-        project_path.chomp!('.wiki') if wiki
+        project_path.chomp!('.wiki') if wiki?
 
         project = Project.find_with_namespace(project_path)
 
         access =
-          if wiki
+          if wiki?
             Gitlab::GitAccessWiki.new(actor, project)
           else
             Gitlab::GitAccess.new(actor, project)
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 04ddfe53ed65729443d0ce3dc9110921d208ba86..abd79b329ae06e54a22ed5ecd5cdf6e8076da288 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -7,6 +7,8 @@ module Filter
     #
     # Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
     class SanitizationFilter < HTML::Pipeline::SanitizationFilter
+      UNSAFE_PROTOCOLS = %w(javascript :javascript data vbscript).freeze
+
       def whitelist
         whitelist = super
 
@@ -43,8 +45,8 @@ def customize_whitelist(whitelist)
         # Allow any protocol in `a` elements...
         whitelist[:protocols].delete('a')
 
-        # ...but then remove links with the `javascript` protocol
-        whitelist[:transformers].push(remove_javascript_links)
+        # ...but then remove links with unsafe protocols
+        whitelist[:transformers].push(remove_unsafe_links)
 
         # Remove `rel` attribute from `a` elements
         whitelist[:transformers].push(remove_rel)
@@ -55,14 +57,14 @@ def customize_whitelist(whitelist)
         whitelist
       end
 
-      def remove_javascript_links
+      def remove_unsafe_links
         lambda do |env|
           node = env[:node]
 
           return unless node.name == 'a'
           return unless node.has_attribute?('href')
 
-          if node['href'].start_with?('javascript', ':javascript')
+          if node['href'].start_with?(*UNSAFE_PROTOCOLS)
             node.remove_attribute('href')
           end
         end
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
index 9e2fb429d577f37fc78365f9a98b3ae42ba8c103..f221afcf73a21b5bb1c802653bd29afa5cf7625a 100644
--- a/lib/tasks/cache.rake
+++ b/lib/tasks/cache.rake
@@ -1,5 +1,5 @@
 namespace :cache do
-  CLEAR_BATCH_SIZE = 1000 # The more the faster, but having too many can crash Ruby
+  CLEAR_BATCH_SIZE = 1000 # There seems to be no speedup when pushing beyond 1,000
   REDIS_SCAN_START_STOP = '0' # Magic value, see http://redis.io/commands/scan
 
   desc "GitLab | Clear redis cache"
diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb
index 774369587113bca181d9b6fdedbfb737731e465b..d4a380cc2ee6942715468616ca87f31a7605349c 100644
--- a/spec/controllers/namespaces_controller_spec.rb
+++ b/spec/controllers/namespaces_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe NamespacesController do
-  let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+  let!(:user) { create(:user, :with_avatar) }
 
   describe "GET show" do
     context "when the namespace belongs to a user" do
diff --git a/spec/controllers/profiles/avatars_controller_spec.rb b/spec/controllers/profiles/avatars_controller_spec.rb
index ad5855df0a433eadd70ae0a74db6435a1cb0cc6d..85dff009bcf9289ed61a21dc5d1d6f5d3685302d 100644
--- a/spec/controllers/profiles/avatars_controller_spec.rb
+++ b/spec/controllers/profiles/avatars_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Profiles::AvatarsController do
-  let(:user)    { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png")) }
+  let(:user)    { create(:user, :with_avatar) }
 
   before do
     sign_in(user)
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index af5d043cf02276ead874f5b16e0a309edce94772..0d9f4b299bc595d9d40713f724a50735422b1079 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe UploadsController do
-  let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+  let!(:user) { create(:user, :with_avatar) }
 
   describe "GET show" do
     context "when viewing a user avatar" do
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 104a5f501432f7c25b520e16b91ef26ec3d4be12..7337ff58be121153a38d51f1cbc967742b46aa5a 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -41,6 +41,7 @@
   end
 
   describe 'GET #calendar' do
+
     it 'renders calendar' do
       sign_in(user)
 
@@ -48,6 +49,23 @@
 
       expect(response).to render_template('calendar')
     end
+
+    context 'forked project' do
+      let!(:project) { create(:project) }
+      let!(:forked_project) { Projects::ForkService.new(project, user).execute }
+
+      before do
+        sign_in(user)
+        project.team << [user, :developer]
+        EventCreateService.new.push(project, user, [])
+        EventCreateService.new.push(forked_project, user, [])
+      end
+
+      it 'includes forked projects' do
+        get :calendar, username: user.username
+        expect(assigns(:contributions_calendar).projects.count).to eq(2)
+      end
+    end
   end
 
   describe 'GET #calendar_activities' do
diff --git a/spec/factories.rb b/spec/factories.rb
index 3ad8226dfb24b31e046916e321648cb3efb16ba5..8f55064db14eef8c71f5b4ca09fa52f8895a02d7 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -32,10 +32,18 @@
       before(:create) do |user|
         user.two_factor_enabled = true
         user.otp_secret = User.generate_otp_secret(32)
+        user.otp_grace_period_started_at = Time.now
         user.generate_otp_backup_codes!
       end
     end
 
+    trait :with_avatar do
+      avatar { fixture_file_upload(Rails.root.join(*%w(spec fixtures dk.png)), 'image/png') }
+      avatar_crop_x 0
+      avatar_crop_y 0
+      avatar_crop_size 256
+    end
+
     factory :omniauth_user do
       ignore do
         extern_uid '123456'
diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb
index b301fa93791506050db31ef6fcc927ee33072d81..16156c302531e8d1003252d33195091efc5e7fee 100644
--- a/spec/factories/appearances.rb
+++ b/spec/factories/appearances.rb
@@ -2,7 +2,12 @@
 
 FactoryGirl.define do
   factory :appearance do
+<<<<<<< HEAD
     title "GitLab Enterprise Edition"
     description "Open source software to collaborate on code"
+=======
+    title       "MepMep"
+    description "This is my Community Edition instance"
+>>>>>>> 204a9895333178cc90b40ef365ad566d750fa594
   end
 end
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
index 38c8d343ce3bd79b1462905104e464fbebec152f..591866b40d46521c012c2a0e9f8b3c131a6126a4 100644
--- a/spec/features/issues/filter_by_milestone_spec.rb
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -13,7 +13,7 @@
     visit_issues(project)
     filter_by_milestone(Milestone::None.title)
 
-    expect(page).to have_css('.title', count: 1)
+    expect(page).to have_css('.issue .title', count: 1)
   end
 
   scenario 'filters by a specific Milestone', js: true do
@@ -23,7 +23,7 @@
     visit_issues(project)
     filter_by_milestone(milestone.title)
 
-    expect(page).to have_css('.title', count: 1)
+    expect(page).to have_css('.issue .title', count: 1)
   end
 
   def visit_issues(project)
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index f6c1005d2655971e5585000f02f797e60a9e17cc..8013b31524f005721b0477e784c415c94b07e3e6 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -77,7 +77,7 @@ def stub_action_name(value)
     let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
 
     it 'should return an url for the avatar' do
-      user = create(:user, avatar: File.open(avatar_file_path))
+      user = create(:user, :with_avatar, avatar: File.open(avatar_file_path))
 
       expect(helper.avatar_icon(user.email).to_s).
         to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
@@ -88,7 +88,7 @@ def stub_action_name(value)
       # Must be stubbed after the stub above, and separately
       stub_config_setting(url: Settings.send(:build_gitlab_url))
 
-      user = create(:user, avatar: File.open(avatar_file_path))
+      user = create(:user, :with_avatar, avatar: File.open(avatar_file_path))
 
       expect(helper.avatar_icon(user.email).to_s).
         to match("/gitlab/uploads/user/avatar/#{user.id}/banana_sample.gif")
@@ -102,7 +102,7 @@ def stub_action_name(value)
 
     describe 'using a User' do
       it 'should return an URL for the avatar' do
-        user = create(:user, avatar: File.open(avatar_file_path))
+        user = create(:user, :with_avatar, avatar: File.open(avatar_file_path))
 
         expect(helper.avatar_icon(user).to_s).
           to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index e14a6dbf9225ae2c54d28831410ead8ef1afae53..4a7b00c7660cdfc827e201a0234bd6c9ff643d3c 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -156,13 +156,27 @@
     }
 
     protocols.each do |name, data|
-      it "handles #{name}" do
+      it "disallows #{name}" do
         doc = filter(data[:input])
 
         expect(doc.to_html).to eq data[:output]
       end
     end
 
+    it 'disallows data links' do
+      input = '<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">XSS</a>'
+      output = filter(input)
+
+      expect(output.to_html).to eq '<a>XSS</a>'
+    end
+
+    it 'disallows vbscript links' do
+      input = '<a href="vbscript:alert(document.domain)">XSS</a>'
+      output = filter(input)
+
+      expect(output.to_html).to eq '<a>XSS</a>'
+    end
+
     it 'allows non-standard anchor schemes' do
       exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>}
       act = filter(exp)
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 645ee0b929a7ff6d0a09fd2f3af9aff175f3adea..983848392b7eb52048649b89e7464ffd19471540 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -19,6 +19,10 @@
 require 'spec_helper'
 
 describe ProjectHook, models: true do
+  describe "Associations" do
+    it { is_expected.to belong_to :project }
+  end
+
   describe '.push_hooks' do
     it 'should return hooks for push events only' do
       hook = create(:project_hook, push_events: true)
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 7070aa4ac620fbbaf670be7c91b3d85736ad1669..6ea99952a8f4021d06308ce9bc10c14b922eabea 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -18,20 +18,14 @@
 
 require 'spec_helper'
 
-describe ProjectHook, models: true do
-  describe "Associations" do
-    it { is_expected.to belong_to :project }
-  end
-
-  describe "Mass assignment" do
-  end
-
+describe WebHook, models: true do
   describe "Validations" do
     it { is_expected.to validate_presence_of(:url) }
 
-    context "url format" do
+    describe 'url' do
       it { is_expected.to allow_value("http://example.com").for(:url) }
-      it { is_expected.to allow_value("https://excample.com").for(:url) }
+      it { is_expected.to allow_value("https://example.com").for(:url) }
+      it { is_expected.to allow_value(" https://example.com ").for(:url) }
       it { is_expected.to allow_value("http://test.com/api").for(:url) }
       it { is_expected.to allow_value("http://test.com/api?key=abc").for(:url) }
       it { is_expected.to allow_value("http://test.com/api?key=abc&type=def").for(:url) }
@@ -39,6 +33,12 @@
       it { is_expected.not_to allow_value("example.com").for(:url) }
       it { is_expected.not_to allow_value("ftp://example.com").for(:url) }
       it { is_expected.not_to allow_value("herp-and-derp").for(:url) }
+
+      it 'strips :url before saving it' do
+        hook = create(:project_hook, url: ' https://example.com ')
+
+        expect(hook.url).to eq('https://example.com')
+      end
     end
   end
 
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index c05afc2c91aec81381e8d8c1d6ea82cd51242a69..a39ca420a68b6e2a06c99610e695579440a8b272 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -362,14 +362,14 @@
       repository.expire_cache('master')
     end
 
-    it 'expires the emptiness cache for an empty repository' do
+    it 'expires the emptiness caches for an empty repository' do
       expect(repository).to receive(:empty?).and_return(true)
       expect(repository).to receive(:expire_emptiness_caches)
 
       repository.expire_cache
     end
 
-    it 'does not expire the emptiness cache for a non-empty repository' do
+    it 'does not expire the emptiness caches for a non-empty repository' do
       expect(repository).to receive(:empty?).and_return(false)
       expect(repository).to_not receive(:expire_emptiness_caches)
 
@@ -465,6 +465,110 @@
     end
   end
 
+  describe '#before_delete' do
+    describe 'when a repository does not exist' do
+      before do
+        allow(repository).to receive(:exists?).and_return(false)
+      end
+
+      it 'does not flush caches that depend on repository data' do
+        expect(repository).to_not receive(:expire_cache)
+
+        repository.before_delete
+      end
+
+      it 'flushes the root ref cache' do
+        expect(repository).to receive(:expire_root_ref_cache)
+
+        repository.before_delete
+      end
+
+      it 'flushes the emptiness caches' do
+        expect(repository).to receive(:expire_emptiness_caches)
+
+        repository.before_delete
+      end
+    end
+
+    describe 'when a repository exists' do
+      before do
+        allow(repository).to receive(:exists?).and_return(true)
+      end
+
+      it 'flushes the caches that depend on repository data' do
+        expect(repository).to receive(:expire_cache)
+
+        repository.before_delete
+      end
+
+      it 'flushes the root ref cache' do
+        expect(repository).to receive(:expire_root_ref_cache)
+
+        repository.before_delete
+      end
+
+      it 'flushes the emptiness caches' do
+        expect(repository).to receive(:expire_emptiness_caches)
+
+        repository.before_delete
+      end
+    end
+  end
+
+  describe '#before_change_head' do
+    it 'flushes the branch cache' do
+      expect(repository).to receive(:expire_branch_cache)
+
+      repository.before_change_head
+    end
+
+    it 'flushes the root ref cache' do
+      expect(repository).to receive(:expire_root_ref_cache)
+
+      repository.before_change_head
+    end
+  end
+
+  describe '#before_create_tag' do
+    it 'flushes the cache' do
+      expect(repository).to receive(:expire_cache)
+
+      repository.before_create_tag
+    end
+  end
+
+  describe '#after_import' do
+    it 'flushes the emptiness cachess' do
+      expect(repository).to receive(:expire_emptiness_caches)
+
+      repository.after_import
+    end
+  end
+
+  describe '#after_push_commit' do
+    it 'flushes the cache' do
+      expect(repository).to receive(:expire_cache).with('master')
+
+      repository.after_push_commit('master')
+    end
+  end
+
+  describe '#after_create_branch' do
+    it 'flushes the visible content cache' do
+      expect(repository).to receive(:expire_has_visible_content_cache)
+
+      repository.after_create_branch
+    end
+  end
+
+  describe '#after_remove_branch' do
+    it 'flushes the visible content cache' do
+      expect(repository).to receive(:expire_has_visible_content_cache)
+
+      repository.after_remove_branch
+    end
+  end
+
   describe "Elastic search", elastic: true do
     before do
       Repository.__elasticsearch__.create_index!
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 308b9f22ceb13e9a83a87cfbf0ab10379efe66df..4bf1c04975404b123a64bbbd1adbc651bc52f3bd 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -174,6 +174,18 @@
         end
       end
     end
+
+    describe 'avatar' do
+      it 'only validates when avatar is present' do
+        user = build(:user, :with_avatar)
+
+        user.avatar_crop_x    = nil
+        user.avatar_crop_y    = nil
+        user.avatar_crop_size = nil
+
+        expect(user).not_to be_valid
+      end
+    end
   end
 
   describe "non_ldap" do
@@ -269,6 +281,7 @@
       expect(user).to be_two_factor_enabled
       expect(user.encrypted_otp_secret).not_to be_nil
       expect(user.otp_backup_codes).not_to be_nil
+      expect(user.otp_grace_period_started_at).not_to be_nil
 
       user.disable_two_factor!
 
@@ -277,6 +290,7 @@
       expect(user.encrypted_otp_secret_iv).to be_nil
       expect(user.encrypted_otp_secret_salt).to be_nil
       expect(user.otp_backup_codes).to be_nil
+      expect(user.otp_grace_period_started_at).to be_nil
     end
   end
 
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 8d0ae1475c2de6de947d39a155ba412a8554820c..22802dd0e05104ab36d0a9ad81a03099c5d7f5e6 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -54,6 +54,18 @@
         project.team << [user, :developer]
       end
 
+      context "git push with project.wiki" do
+        it 'responds with success' do
+          project_wiki = create(:project, name: 'my.wiki', path: 'my.wiki')
+          project_wiki.team << [user, :developer]
+
+          push(key, project_wiki)
+
+          expect(response.status).to eq(200)
+          expect(json_response["status"]).to be_truthy
+        end
+      end
+
       context "git pull" do
         it do
           pull(key, project)
diff --git a/vendor/assets/javascripts/cropper.js b/vendor/assets/javascripts/cropper.js
new file mode 100755
index 0000000000000000000000000000000000000000..84aa6119ec3a9cbeeae99a57bea24235fb26d0c8
--- /dev/null
+++ b/vendor/assets/javascripts/cropper.js
@@ -0,0 +1,2972 @@
+/*!
+ * Cropper v2.2.5
+ * https://github.com/fengyuanchen/cropper
+ *
+ * Copyright (c) 2014-2016 Fengyuan Chen and contributors
+ * Released under the MIT license
+ *
+ * Date: 2016-01-18T05:42:50.800Z
+ */
+
+(function (factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as anonymous module.
+    define(['jquery'], factory);
+  } else if (typeof exports === 'object') {
+    // Node / CommonJS
+    factory(require('jquery'));
+  } else {
+    // Browser globals.
+    factory(jQuery);
+  }
+})(function ($) {
+
+  'use strict';
+
+  // Globals
+  var $window = $(window);
+  var $document = $(document);
+  var location = window.location;
+  var ArrayBuffer = window.ArrayBuffer;
+  var Uint8Array = window.Uint8Array;
+  var DataView = window.DataView;
+  var btoa = window.btoa;
+
+  // Constants
+  var NAMESPACE = 'cropper';
+
+  // Classes
+  var CLASS_MODAL = 'cropper-modal';
+  var CLASS_HIDE = 'cropper-hide';
+  var CLASS_HIDDEN = 'cropper-hidden';
+  var CLASS_INVISIBLE = 'cropper-invisible';
+  var CLASS_MOVE = 'cropper-move';
+  var CLASS_CROP = 'cropper-crop';
+  var CLASS_DISABLED = 'cropper-disabled';
+  var CLASS_BG = 'cropper-bg';
+
+  // Events
+  var EVENT_MOUSE_DOWN = 'mousedown touchstart pointerdown MSPointerDown';
+  var EVENT_MOUSE_MOVE = 'mousemove touchmove pointermove MSPointerMove';
+  var EVENT_MOUSE_UP = 'mouseup touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel';
+  var EVENT_WHEEL = 'wheel mousewheel DOMMouseScroll';
+  var EVENT_DBLCLICK = 'dblclick';
+  var EVENT_LOAD = 'load.' + NAMESPACE;
+  var EVENT_ERROR = 'error.' + NAMESPACE;
+  var EVENT_RESIZE = 'resize.' + NAMESPACE; // Bind to window with namespace
+  var EVENT_BUILD = 'build.' + NAMESPACE;
+  var EVENT_BUILT = 'built.' + NAMESPACE;
+  var EVENT_CROP_START = 'cropstart.' + NAMESPACE;
+  var EVENT_CROP_MOVE = 'cropmove.' + NAMESPACE;
+  var EVENT_CROP_END = 'cropend.' + NAMESPACE;
+  var EVENT_CROP = 'crop.' + NAMESPACE;
+  var EVENT_ZOOM = 'zoom.' + NAMESPACE;
+
+  // RegExps
+  var REGEXP_ACTIONS = /e|w|s|n|se|sw|ne|nw|all|crop|move|zoom/;
+  var REGEXP_DATA_URL = /^data\:/;
+  var REGEXP_DATA_URL_HEAD = /^data\:([^\;]+)\;base64,/;
+  var REGEXP_DATA_URL_JPEG = /^data\:image\/jpeg.*;base64,/;
+
+  // Data keys
+  var DATA_PREVIEW = 'preview';
+  var DATA_ACTION = 'action';
+
+  // Actions
+  var ACTION_EAST = 'e';
+  var ACTION_WEST = 'w';
+  var ACTION_SOUTH = 's';
+  var ACTION_NORTH = 'n';
+  var ACTION_SOUTH_EAST = 'se';
+  var ACTION_SOUTH_WEST = 'sw';
+  var ACTION_NORTH_EAST = 'ne';
+  var ACTION_NORTH_WEST = 'nw';
+  var ACTION_ALL = 'all';
+  var ACTION_CROP = 'crop';
+  var ACTION_MOVE = 'move';
+  var ACTION_ZOOM = 'zoom';
+  var ACTION_NONE = 'none';
+
+  // Supports
+  var SUPPORT_CANVAS = $.isFunction($('<canvas>')[0].getContext);
+
+  // Maths
+  var num = Number;
+  var min = Math.min;
+  var max = Math.max;
+  var abs = Math.abs;
+  var sin = Math.sin;
+  var cos = Math.cos;
+  var sqrt = Math.sqrt;
+  var round = Math.round;
+  var floor = Math.floor;
+
+  // Utilities
+  var fromCharCode = String.fromCharCode;
+
+  function isNumber(n) {
+    return typeof n === 'number' && !isNaN(n);
+  }
+
+  function isUndefined(n) {
+    return typeof n === 'undefined';
+  }
+
+  function toArray(obj, offset) {
+    var args = [];
+
+    // This is necessary for IE8
+    if (isNumber(offset)) {
+      args.push(offset);
+    }
+
+    return args.slice.apply(obj, args);
+  }
+
+  // Custom proxy to avoid jQuery's guid
+  function proxy(fn, context) {
+    var args = toArray(arguments, 2);
+
+    return function () {
+      return fn.apply(context, args.concat(toArray(arguments)));
+    };
+  }
+
+  function isCrossOriginURL(url) {
+    var parts = url.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);
+
+    return parts && (
+      parts[1] !== location.protocol ||
+      parts[2] !== location.hostname ||
+      parts[3] !== location.port
+    );
+  }
+
+  function addTimestamp(url) {
+    var timestamp = 'timestamp=' + (new Date()).getTime();
+
+    return (url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp);
+  }
+
+  function getCrossOrigin(crossOrigin) {
+    return crossOrigin ? ' crossOrigin="' + crossOrigin + '"' : '';
+  }
+
+  function getImageSize(image, callback) {
+    var newImage;
+
+    // Modern browsers
+    if (image.naturalWidth) {
+      return callback(image.naturalWidth, image.naturalHeight);
+    }
+
+    // IE8: Don't use `new Image()` here (#319)
+    newImage = document.createElement('img');
+
+    newImage.onload = function () {
+      callback(this.width, this.height);
+    };
+
+    newImage.src = image.src;
+  }
+
+  function getTransform(options) {
+    var transforms = [];
+    var rotate = options.rotate;
+    var scaleX = options.scaleX;
+    var scaleY = options.scaleY;
+
+    if (isNumber(rotate)) {
+      transforms.push('rotate(' + rotate + 'deg)');
+    }
+
+    if (isNumber(scaleX) && isNumber(scaleY)) {
+      transforms.push('scale(' + scaleX + ',' + scaleY + ')');
+    }
+
+    return transforms.length ? transforms.join(' ') : 'none';
+  }
+
+  function getRotatedSizes(data, isReversed) {
+    var deg = abs(data.degree) % 180;
+    var arc = (deg > 90 ? (180 - deg) : deg) * Math.PI / 180;
+    var sinArc = sin(arc);
+    var cosArc = cos(arc);
+    var width = data.width;
+    var height = data.height;
+    var aspectRatio = data.aspectRatio;
+    var newWidth;
+    var newHeight;
+
+    if (!isReversed) {
+      newWidth = width * cosArc + height * sinArc;
+      newHeight = width * sinArc + height * cosArc;
+    } else {
+      newWidth = width / (cosArc + sinArc / aspectRatio);
+      newHeight = newWidth / aspectRatio;
+    }
+
+    return {
+      width: newWidth,
+      height: newHeight
+    };
+  }
+
+  function getSourceCanvas(image, data) {
+    var canvas = $('<canvas>')[0];
+    var context = canvas.getContext('2d');
+    var x = 0;
+    var y = 0;
+    var width = data.naturalWidth;
+    var height = data.naturalHeight;
+    var rotate = data.rotate;
+    var scaleX = data.scaleX;
+    var scaleY = data.scaleY;
+    var scalable = isNumber(scaleX) && isNumber(scaleY) && (scaleX !== 1 || scaleY !== 1);
+    var rotatable = isNumber(rotate) && rotate !== 0;
+    var advanced = rotatable || scalable;
+    var canvasWidth = width;
+    var canvasHeight = height;
+    var translateX;
+    var translateY;
+    var rotated;
+
+    if (scalable) {
+      translateX = width / 2;
+      translateY = height / 2;
+    }
+
+    if (rotatable) {
+      rotated = getRotatedSizes({
+        width: width,
+        height: height,
+        degree: rotate
+      });
+
+      canvasWidth = rotated.width;
+      canvasHeight = rotated.height;
+      translateX = rotated.width / 2;
+      translateY = rotated.height / 2;
+    }
+
+    canvas.width = canvasWidth;
+    canvas.height = canvasHeight;
+
+    if (advanced) {
+      x = -width / 2;
+      y = -height / 2;
+
+      context.save();
+      context.translate(translateX, translateY);
+    }
+
+    if (rotatable) {
+      context.rotate(rotate * Math.PI / 180);
+    }
+
+    // Should call `scale` after rotated
+    if (scalable) {
+      context.scale(scaleX, scaleY);
+    }
+
+    context.drawImage(image, floor(x), floor(y), floor(width), floor(height));
+
+    if (advanced) {
+      context.restore();
+    }
+
+    return canvas;
+  }
+
+  function getTouchesCenter(touches) {
+    var length = touches.length;
+    var pageX = 0;
+    var pageY = 0;
+
+    if (length) {
+      $.each(touches, function (i, touch) {
+        pageX += touch.pageX;
+        pageY += touch.pageY;
+      });
+
+      pageX /= length;
+      pageY /= length;
+    }
+
+    return {
+      pageX: pageX,
+      pageY: pageY
+    };
+  }
+
+  function getStringFromCharCode(dataView, start, length) {
+    var str = '';
+    var i;
+
+    for (i = start, length += start; i < length; i++) {
+      str += fromCharCode(dataView.getUint8(i));
+    }
+
+    return str;
+  }
+
+  function getOrientation(arrayBuffer) {
+    var dataView = new DataView(arrayBuffer);
+    var length = dataView.byteLength;
+    var orientation;
+    var exifIDCode;
+    var tiffOffset;
+    var firstIFDOffset;
+    var littleEndian;
+    var endianness;
+    var app1Start;
+    var ifdStart;
+    var offset;
+    var i;
+
+    // Only handle JPEG image (start by 0xFFD8)
+    if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
+      offset = 2;
+
+      while (offset < length) {
+        if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
+          app1Start = offset;
+          break;
+        }
+
+        offset++;
+      }
+    }
+
+    if (app1Start) {
+      exifIDCode = app1Start + 4;
+      tiffOffset = app1Start + 10;
+
+      if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
+        endianness = dataView.getUint16(tiffOffset);
+        littleEndian = endianness === 0x4949;
+
+        if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
+          if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
+            firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
+
+            if (firstIFDOffset >= 0x00000008) {
+              ifdStart = tiffOffset + firstIFDOffset;
+            }
+          }
+        }
+      }
+    }
+
+    if (ifdStart) {
+      length = dataView.getUint16(ifdStart, littleEndian);
+
+      for (i = 0; i < length; i++) {
+        offset = ifdStart + i * 12 + 2;
+
+        if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
+
+          // 8 is the offset of the current tag's value
+          offset += 8;
+
+          // Get the original orientation value
+          orientation = dataView.getUint16(offset, littleEndian);
+
+          // Override the orientation with the default value: 1
+          dataView.setUint16(offset, 1, littleEndian);
+          break;
+        }
+      }
+    }
+
+    return orientation;
+  }
+
+  function dataURLToArrayBuffer(dataURL) {
+    var base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, '');
+    var binary = atob(base64);
+    var length = binary.length;
+    var arrayBuffer = new ArrayBuffer(length);
+    var dataView = new Uint8Array(arrayBuffer);
+    var i;
+
+    for (i = 0; i < length; i++) {
+      dataView[i] = binary.charCodeAt(i);
+    }
+
+    return arrayBuffer;
+  }
+
+  // Only available for JPEG image
+  function arrayBufferToDataURL(arrayBuffer) {
+    var dataView = new Uint8Array(arrayBuffer);
+    var length = dataView.length;
+    var base64 = '';
+    var i;
+
+    for (i = 0; i < length; i++) {
+      base64 += fromCharCode(dataView[i]);
+    }
+
+    return 'data:image/jpeg;base64,' + btoa(base64);
+  }
+
+  function Cropper(element, options) {
+    this.$element = $(element);
+    this.options = $.extend({}, Cropper.DEFAULTS, $.isPlainObject(options) && options);
+    this.isLoaded = false;
+    this.isBuilt = false;
+    this.isCompleted = false;
+    this.isRotated = false;
+    this.isCropped = false;
+    this.isDisabled = false;
+    this.isReplaced = false;
+    this.isLimited = false;
+    this.wheeling = false;
+    this.isImg = false;
+    this.originalUrl = '';
+    this.canvas = null;
+    this.cropBox = null;
+    this.init();
+  }
+
+  Cropper.prototype = {
+    constructor: Cropper,
+
+    init: function () {
+      var $this = this.$element;
+      var url;
+
+      if ($this.is('img')) {
+        this.isImg = true;
+
+        // Should use `$.fn.attr` here. e.g.: "img/picture.jpg"
+        this.originalUrl = url = $this.attr('src');
+
+        // Stop when it's a blank image
+        if (!url) {
+          return;
+        }
+
+        // Should use `$.fn.prop` here. e.g.: "http://example.com/img/picture.jpg"
+        url = $this.prop('src');
+      } else if ($this.is('canvas') && SUPPORT_CANVAS) {
+        url = $this[0].toDataURL();
+      }
+
+      this.load(url);
+    },
+
+    // A shortcut for triggering custom events
+    trigger: function (type, data) {
+      var e = $.Event(type, data);
+
+      this.$element.trigger(e);
+
+      return e;
+    },
+
+    load: function (url) {
+      var options = this.options;
+      var $this = this.$element;
+      var read;
+      var xhr;
+
+      if (!url) {
+        return;
+      }
+
+      // Trigger build event first
+      $this.one(EVENT_BUILD, options.build);
+
+      if (this.trigger(EVENT_BUILD).isDefaultPrevented()) {
+        return;
+      }
+
+      this.url = url;
+      this.image = {};
+
+      if (!options.checkOrientation || !ArrayBuffer) {
+        return this.clone();
+      }
+
+      read = $.proxy(this.read, this);
+
+      // XMLHttpRequest disallows to open a Data URL in some browsers like IE11 and Safari
+      if (REGEXP_DATA_URL.test(url)) {
+        return REGEXP_DATA_URL_JPEG.test(url) ?
+          read(dataURLToArrayBuffer(url)) :
+          this.clone();
+      }
+
+      xhr = new XMLHttpRequest();
+
+      xhr.onerror = xhr.onabort = $.proxy(function () {
+        this.clone();
+      }, this);
+
+      xhr.onload = function () {
+        read(this.response);
+      };
+
+      xhr.open('get', url);
+      xhr.responseType = 'arraybuffer';
+      xhr.send();
+    },
+
+    read: function (arrayBuffer) {
+      var options = this.options;
+      var orientation = getOrientation(arrayBuffer);
+      var image = this.image;
+      var rotate;
+      var scaleX;
+      var scaleY;
+
+      if (orientation > 1) {
+        this.url = arrayBufferToDataURL(arrayBuffer);
+
+        switch (orientation) {
+
+          // flip horizontal
+          case 2:
+            scaleX = -1;
+            break;
+
+          // rotate left 180°
+          case 3:
+            rotate = -180;
+            break;
+
+          // flip vertical
+          case 4:
+            scaleY = -1;
+            break;
+
+          // flip vertical + rotate right 90°
+          case 5:
+            rotate = 90;
+            scaleY = -1;
+            break;
+
+          // rotate right 90°
+          case 6:
+            rotate = 90;
+            break;
+
+          // flip horizontal + rotate right 90°
+          case 7:
+            rotate = 90;
+            scaleX = -1;
+            break;
+
+          // rotate left 90°
+          case 8:
+            rotate = -90;
+            break;
+        }
+      }
+
+      if (options.rotatable) {
+        image.rotate = rotate;
+      }
+
+      if (options.scalable) {
+        image.scaleX = scaleX;
+        image.scaleY = scaleY;
+      }
+
+      this.clone();
+    },
+
+    clone: function () {
+      var options = this.options;
+      var $this = this.$element;
+      var url = this.url;
+      var crossOrigin = '';
+      var crossOriginUrl;
+      var $clone;
+
+      if (options.checkCrossOrigin && isCrossOriginURL(url)) {
+        crossOrigin = $this.prop('crossOrigin');
+
+        if (crossOrigin) {
+          crossOriginUrl = url;
+        } else {
+          crossOrigin = 'anonymous';
+
+          // Bust cache (#148) when there is not a "crossOrigin" property
+          crossOriginUrl = addTimestamp(url);
+        }
+      }
+
+      this.crossOrigin = crossOrigin;
+      this.crossOriginUrl = crossOriginUrl;
+      this.$clone = $clone = $('<img' + getCrossOrigin(crossOrigin) + ' src="' + (crossOriginUrl || url) + '">');
+
+      if (this.isImg) {
+        if ($this[0].complete) {
+          this.start();
+        } else {
+          $this.one(EVENT_LOAD, $.proxy(this.start, this));
+        }
+      } else {
+        $clone.
+          one(EVENT_LOAD, $.proxy(this.start, this)).
+          one(EVENT_ERROR, $.proxy(this.stop, this)).
+          addClass(CLASS_HIDE).
+          insertAfter($this);
+      }
+    },
+
+    start: function () {
+      var $image = this.$element;
+      var $clone = this.$clone;
+
+      if (!this.isImg) {
+        $clone.off(EVENT_ERROR, this.stop);
+        $image = $clone;
+      }
+
+      getImageSize($image[0], $.proxy(function (naturalWidth, naturalHeight) {
+        $.extend(this.image, {
+          naturalWidth: naturalWidth,
+          naturalHeight: naturalHeight,
+          aspectRatio: naturalWidth / naturalHeight
+        });
+
+        this.isLoaded = true;
+        this.build();
+      }, this));
+    },
+
+    stop: function () {
+      this.$clone.remove();
+      this.$clone = null;
+    },
+
+    build: function () {
+      var options = this.options;
+      var $this = this.$element;
+      var $clone = this.$clone;
+      var $cropper;
+      var $cropBox;
+      var $face;
+
+      if (!this.isLoaded) {
+        return;
+      }
+
+      // Unbuild first when replace
+      if (this.isBuilt) {
+        this.unbuild();
+      }
+
+      // Create cropper elements
+      this.$container = $this.parent();
+      this.$cropper = $cropper = $(Cropper.TEMPLATE);
+      this.$canvas = $cropper.find('.cropper-canvas').append($clone);
+      this.$dragBox = $cropper.find('.cropper-drag-box');
+      this.$cropBox = $cropBox = $cropper.find('.cropper-crop-box');
+      this.$viewBox = $cropper.find('.cropper-view-box');
+      this.$face = $face = $cropBox.find('.cropper-face');
+
+      // Hide the original image
+      $this.addClass(CLASS_HIDDEN).after($cropper);
+
+      // Show the clone image if is hidden
+      if (!this.isImg) {
+        $clone.removeClass(CLASS_HIDE);
+      }
+
+      this.initPreview();
+      this.bind();
+
+      options.aspectRatio = max(0, options.aspectRatio) || NaN;
+      options.viewMode = max(0, min(3, round(options.viewMode))) || 0;
+
+      if (options.autoCrop) {
+        this.isCropped = true;
+
+        if (options.modal) {
+          this.$dragBox.addClass(CLASS_MODAL);
+        }
+      } else {
+        $cropBox.addClass(CLASS_HIDDEN);
+      }
+
+      if (!options.guides) {
+        $cropBox.find('.cropper-dashed').addClass(CLASS_HIDDEN);
+      }
+
+      if (!options.center) {
+        $cropBox.find('.cropper-center').addClass(CLASS_HIDDEN);
+      }
+
+      if (options.cropBoxMovable) {
+        $face.addClass(CLASS_MOVE).data(DATA_ACTION, ACTION_ALL);
+      }
+
+      if (!options.highlight) {
+        $face.addClass(CLASS_INVISIBLE);
+      }
+
+      if (options.background) {
+        $cropper.addClass(CLASS_BG);
+      }
+
+      if (!options.cropBoxResizable) {
+        $cropBox.find('.cropper-line, .cropper-point').addClass(CLASS_HIDDEN);
+      }
+
+      this.setDragMode(options.dragMode);
+      this.render();
+      this.isBuilt = true;
+      this.setData(options.data);
+      $this.one(EVENT_BUILT, options.built);
+
+      // Trigger the built event asynchronously to keep `data('cropper')` is defined
+      setTimeout($.proxy(function () {
+        this.trigger(EVENT_BUILT);
+        this.isCompleted = true;
+      }, this), 0);
+    },
+
+    unbuild: function () {
+      if (!this.isBuilt) {
+        return;
+      }
+
+      this.isBuilt = false;
+      this.isCompleted = false;
+      this.initialImage = null;
+
+      // Clear `initialCanvas` is necessary when replace
+      this.initialCanvas = null;
+      this.initialCropBox = null;
+      this.container = null;
+      this.canvas = null;
+
+      // Clear `cropBox` is necessary when replace
+      this.cropBox = null;
+      this.unbind();
+
+      this.resetPreview();
+      this.$preview = null;
+
+      this.$viewBox = null;
+      this.$cropBox = null;
+      this.$dragBox = null;
+      this.$canvas = null;
+      this.$container = null;
+
+      this.$cropper.remove();
+      this.$cropper = null;
+    },
+
+    render: function () {
+      this.initContainer();
+      this.initCanvas();
+      this.initCropBox();
+
+      this.renderCanvas();
+
+      if (this.isCropped) {
+        this.renderCropBox();
+      }
+    },
+
+    initContainer: function () {
+      var options = this.options;
+      var $this = this.$element;
+      var $container = this.$container;
+      var $cropper = this.$cropper;
+
+      $cropper.addClass(CLASS_HIDDEN);
+      $this.removeClass(CLASS_HIDDEN);
+
+      $cropper.css((this.container = {
+        width: max($container.width(), num(options.minContainerWidth) || 200),
+        height: max($container.height(), num(options.minContainerHeight) || 100)
+      }));
+
+      $this.addClass(CLASS_HIDDEN);
+      $cropper.removeClass(CLASS_HIDDEN);
+    },
+
+    // Canvas (image wrapper)
+    initCanvas: function () {
+      var viewMode = this.options.viewMode;
+      var container = this.container;
+      var containerWidth = container.width;
+      var containerHeight = container.height;
+      var image = this.image;
+      var imageNaturalWidth = image.naturalWidth;
+      var imageNaturalHeight = image.naturalHeight;
+      var is90Degree = abs(image.rotate) === 90;
+      var naturalWidth = is90Degree ? imageNaturalHeight : imageNaturalWidth;
+      var naturalHeight = is90Degree ? imageNaturalWidth : imageNaturalHeight;
+      var aspectRatio = naturalWidth / naturalHeight;
+      var canvasWidth = containerWidth;
+      var canvasHeight = containerHeight;
+      var canvas;
+
+      if (containerHeight * aspectRatio > containerWidth) {
+        if (viewMode === 3) {
+          canvasWidth = containerHeight * aspectRatio;
+        } else {
+          canvasHeight = containerWidth / aspectRatio;
+        }
+      } else {
+        if (viewMode === 3) {
+          canvasHeight = containerWidth / aspectRatio;
+        } else {
+          canvasWidth = containerHeight * aspectRatio;
+        }
+      }
+
+      canvas = {
+        naturalWidth: naturalWidth,
+        naturalHeight: naturalHeight,
+        aspectRatio: aspectRatio,
+        width: canvasWidth,
+        height: canvasHeight
+      };
+
+      canvas.oldLeft = canvas.left = (containerWidth - canvasWidth) / 2;
+      canvas.oldTop = canvas.top = (containerHeight - canvasHeight) / 2;
+
+      this.canvas = canvas;
+      this.isLimited = (viewMode === 1 || viewMode === 2);
+      this.limitCanvas(true, true);
+      this.initialImage = $.extend({}, image);
+      this.initialCanvas = $.extend({}, canvas);
+    },
+
+    limitCanvas: function (isSizeLimited, isPositionLimited) {
+      var options = this.options;
+      var viewMode = options.viewMode;
+      var container = this.container;
+      var containerWidth = container.width;
+      var containerHeight = container.height;
+      var canvas = this.canvas;
+      var aspectRatio = canvas.aspectRatio;
+      var cropBox = this.cropBox;
+      var isCropped = this.isCropped && cropBox;
+      var minCanvasWidth;
+      var minCanvasHeight;
+      var newCanvasLeft;
+      var newCanvasTop;
+
+      if (isSizeLimited) {
+        minCanvasWidth = num(options.minCanvasWidth) || 0;
+        minCanvasHeight = num(options.minCanvasHeight) || 0;
+
+        if (viewMode) {
+          if (viewMode > 1) {
+            minCanvasWidth = max(minCanvasWidth, containerWidth);
+            minCanvasHeight = max(minCanvasHeight, containerHeight);
+
+            if (viewMode === 3) {
+              if (minCanvasHeight * aspectRatio > minCanvasWidth) {
+                minCanvasWidth = minCanvasHeight * aspectRatio;
+              } else {
+                minCanvasHeight = minCanvasWidth / aspectRatio;
+              }
+            }
+          } else {
+            if (minCanvasWidth) {
+              minCanvasWidth = max(minCanvasWidth, isCropped ? cropBox.width : 0);
+            } else if (minCanvasHeight) {
+              minCanvasHeight = max(minCanvasHeight, isCropped ? cropBox.height : 0);
+            } else if (isCropped) {
+              minCanvasWidth = cropBox.width;
+              minCanvasHeight = cropBox.height;
+
+              if (minCanvasHeight * aspectRatio > minCanvasWidth) {
+                minCanvasWidth = minCanvasHeight * aspectRatio;
+              } else {
+                minCanvasHeight = minCanvasWidth / aspectRatio;
+              }
+            }
+          }
+        }
+
+        if (minCanvasWidth && minCanvasHeight) {
+          if (minCanvasHeight * aspectRatio > minCanvasWidth) {
+            minCanvasHeight = minCanvasWidth / aspectRatio;
+          } else {
+            minCanvasWidth = minCanvasHeight * aspectRatio;
+          }
+        } else if (minCanvasWidth) {
+          minCanvasHeight = minCanvasWidth / aspectRatio;
+        } else if (minCanvasHeight) {
+          minCanvasWidth = minCanvasHeight * aspectRatio;
+        }
+
+        canvas.minWidth = minCanvasWidth;
+        canvas.minHeight = minCanvasHeight;
+        canvas.maxWidth = Infinity;
+        canvas.maxHeight = Infinity;
+      }
+
+      if (isPositionLimited) {
+        if (viewMode) {
+          newCanvasLeft = containerWidth - canvas.width;
+          newCanvasTop = containerHeight - canvas.height;
+
+          canvas.minLeft = min(0, newCanvasLeft);
+          canvas.minTop = min(0, newCanvasTop);
+          canvas.maxLeft = max(0, newCanvasLeft);
+          canvas.maxTop = max(0, newCanvasTop);
+
+          if (isCropped && this.isLimited) {
+            canvas.minLeft = min(
+              cropBox.left,
+              cropBox.left + cropBox.width - canvas.width
+            );
+            canvas.minTop = min(
+              cropBox.top,
+              cropBox.top + cropBox.height - canvas.height
+            );
+            canvas.maxLeft = cropBox.left;
+            canvas.maxTop = cropBox.top;
+
+            if (viewMode === 2) {
+              if (canvas.width >= containerWidth) {
+                canvas.minLeft = min(0, newCanvasLeft);
+                canvas.maxLeft = max(0, newCanvasLeft);
+              }
+
+              if (canvas.height >= containerHeight) {
+                canvas.minTop = min(0, newCanvasTop);
+                canvas.maxTop = max(0, newCanvasTop);
+              }
+            }
+          }
+        } else {
+          canvas.minLeft = -canvas.width;
+          canvas.minTop = -canvas.height;
+          canvas.maxLeft = containerWidth;
+          canvas.maxTop = containerHeight;
+        }
+      }
+    },
+
+    renderCanvas: function (isChanged) {
+      var canvas = this.canvas;
+      var image = this.image;
+      var rotate = image.rotate;
+      var naturalWidth = image.naturalWidth;
+      var naturalHeight = image.naturalHeight;
+      var aspectRatio;
+      var rotated;
+
+      if (this.isRotated) {
+        this.isRotated = false;
+
+        // Computes rotated sizes with image sizes
+        rotated = getRotatedSizes({
+          width: image.width,
+          height: image.height,
+          degree: rotate
+        });
+
+        aspectRatio = rotated.width / rotated.height;
+
+        if (aspectRatio !== canvas.aspectRatio) {
+          canvas.left -= (rotated.width - canvas.width) / 2;
+          canvas.top -= (rotated.height - canvas.height) / 2;
+          canvas.width = rotated.width;
+          canvas.height = rotated.height;
+          canvas.aspectRatio = aspectRatio;
+          canvas.naturalWidth = naturalWidth;
+          canvas.naturalHeight = naturalHeight;
+
+          // Computes rotated sizes with natural image sizes
+          if (rotate % 180) {
+            rotated = getRotatedSizes({
+              width: naturalWidth,
+              height: naturalHeight,
+              degree: rotate
+            });
+
+            canvas.naturalWidth = rotated.width;
+            canvas.naturalHeight = rotated.height;
+          }
+
+          this.limitCanvas(true, false);
+        }
+      }
+
+      if (canvas.width > canvas.maxWidth || canvas.width < canvas.minWidth) {
+        canvas.left = canvas.oldLeft;
+      }
+
+      if (canvas.height > canvas.maxHeight || canvas.height < canvas.minHeight) {
+        canvas.top = canvas.oldTop;
+      }
+
+      canvas.width = min(max(canvas.width, canvas.minWidth), canvas.maxWidth);
+      canvas.height = min(max(canvas.height, canvas.minHeight), canvas.maxHeight);
+
+      this.limitCanvas(false, true);
+
+      canvas.oldLeft = canvas.left = min(max(canvas.left, canvas.minLeft), canvas.maxLeft);
+      canvas.oldTop = canvas.top = min(max(canvas.top, canvas.minTop), canvas.maxTop);
+
+      this.$canvas.css({
+        width: canvas.width,
+        height: canvas.height,
+        left: canvas.left,
+        top: canvas.top
+      });
+
+      this.renderImage();
+
+      if (this.isCropped && this.isLimited) {
+        this.limitCropBox(true, true);
+      }
+
+      if (isChanged) {
+        this.output();
+      }
+    },
+
+    renderImage: function (isChanged) {
+      var canvas = this.canvas;
+      var image = this.image;
+      var reversed;
+
+      if (image.rotate) {
+        reversed = getRotatedSizes({
+          width: canvas.width,
+          height: canvas.height,
+          degree: image.rotate,
+          aspectRatio: image.aspectRatio
+        }, true);
+      }
+
+      $.extend(image, reversed ? {
+        width: reversed.width,
+        height: reversed.height,
+        left: (canvas.width - reversed.width) / 2,
+        top: (canvas.height - reversed.height) / 2
+      } : {
+        width: canvas.width,
+        height: canvas.height,
+        left: 0,
+        top: 0
+      });
+
+      this.$clone.css({
+        width: image.width,
+        height: image.height,
+        marginLeft: image.left,
+        marginTop: image.top,
+        transform: getTransform(image)
+      });
+
+      if (isChanged) {
+        this.output();
+      }
+    },
+
+    initCropBox: function () {
+      var options = this.options;
+      var canvas = this.canvas;
+      var aspectRatio = options.aspectRatio;
+      var autoCropArea = num(options.autoCropArea) || 0.8;
+      var cropBox = {
+            width: canvas.width,
+            height: canvas.height
+          };
+
+      if (aspectRatio) {
+        if (canvas.height * aspectRatio > canvas.width) {
+          cropBox.height = cropBox.width / aspectRatio;
+        } else {
+          cropBox.width = cropBox.height * aspectRatio;
+        }
+      }
+
+      this.cropBox = cropBox;
+      this.limitCropBox(true, true);
+
+      // Initialize auto crop area
+      cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth);
+      cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight);
+
+      // The width of auto crop area must large than "minWidth", and the height too. (#164)
+      cropBox.width = max(cropBox.minWidth, cropBox.width * autoCropArea);
+      cropBox.height = max(cropBox.minHeight, cropBox.height * autoCropArea);
+      cropBox.oldLeft = cropBox.left = canvas.left + (canvas.width - cropBox.width) / 2;
+      cropBox.oldTop = cropBox.top = canvas.top + (canvas.height - cropBox.height) / 2;
+
+      this.initialCropBox = $.extend({}, cropBox);
+    },
+
+    limitCropBox: function (isSizeLimited, isPositionLimited) {
+      var options = this.options;
+      var aspectRatio = options.aspectRatio;
+      var container = this.container;
+      var containerWidth = container.width;
+      var containerHeight = container.height;
+      var canvas = this.canvas;
+      var cropBox = this.cropBox;
+      var isLimited = this.isLimited;
+      var minCropBoxWidth;
+      var minCropBoxHeight;
+      var maxCropBoxWidth;
+      var maxCropBoxHeight;
+
+      if (isSizeLimited) {
+        minCropBoxWidth = num(options.minCropBoxWidth) || 0;
+        minCropBoxHeight = num(options.minCropBoxHeight) || 0;
+
+        // The min/maxCropBoxWidth/Height must be less than containerWidth/Height
+        minCropBoxWidth = min(minCropBoxWidth, containerWidth);
+        minCropBoxHeight = min(minCropBoxHeight, containerHeight);
+        maxCropBoxWidth = min(containerWidth, isLimited ? canvas.width : containerWidth);
+        maxCropBoxHeight = min(containerHeight, isLimited ? canvas.height : containerHeight);
+
+        if (aspectRatio) {
+          if (minCropBoxWidth && minCropBoxHeight) {
+            if (minCropBoxHeight * aspectRatio > minCropBoxWidth) {
+              minCropBoxHeight = minCropBoxWidth / aspectRatio;
+            } else {
+              minCropBoxWidth = minCropBoxHeight * aspectRatio;
+            }
+          } else if (minCropBoxWidth) {
+            minCropBoxHeight = minCropBoxWidth / aspectRatio;
+          } else if (minCropBoxHeight) {
+            minCropBoxWidth = minCropBoxHeight * aspectRatio;
+          }
+
+          if (maxCropBoxHeight * aspectRatio > maxCropBoxWidth) {
+            maxCropBoxHeight = maxCropBoxWidth / aspectRatio;
+          } else {
+            maxCropBoxWidth = maxCropBoxHeight * aspectRatio;
+          }
+        }
+
+        // The minWidth/Height must be less than maxWidth/Height
+        cropBox.minWidth = min(minCropBoxWidth, maxCropBoxWidth);
+        cropBox.minHeight = min(minCropBoxHeight, maxCropBoxHeight);
+        cropBox.maxWidth = maxCropBoxWidth;
+        cropBox.maxHeight = maxCropBoxHeight;
+      }
+
+      if (isPositionLimited) {
+        if (isLimited) {
+          cropBox.minLeft = max(0, canvas.left);
+          cropBox.minTop = max(0, canvas.top);
+          cropBox.maxLeft = min(containerWidth, canvas.left + canvas.width) - cropBox.width;
+          cropBox.maxTop = min(containerHeight, canvas.top + canvas.height) - cropBox.height;
+        } else {
+          cropBox.minLeft = 0;
+          cropBox.minTop = 0;
+          cropBox.maxLeft = containerWidth - cropBox.width;
+          cropBox.maxTop = containerHeight - cropBox.height;
+        }
+      }
+    },
+
+    renderCropBox: function () {
+      var options = this.options;
+      var container = this.container;
+      var containerWidth = container.width;
+      var containerHeight = container.height;
+      var cropBox = this.cropBox;
+
+      if (cropBox.width > cropBox.maxWidth || cropBox.width < cropBox.minWidth) {
+        cropBox.left = cropBox.oldLeft;
+      }
+
+      if (cropBox.height > cropBox.maxHeight || cropBox.height < cropBox.minHeight) {
+        cropBox.top = cropBox.oldTop;
+      }
+
+      cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth);
+      cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight);
+
+      this.limitCropBox(false, true);
+
+      cropBox.oldLeft = cropBox.left = min(max(cropBox.left, cropBox.minLeft), cropBox.maxLeft);
+      cropBox.oldTop = cropBox.top = min(max(cropBox.top, cropBox.minTop), cropBox.maxTop);
+
+      if (options.movable && options.cropBoxMovable) {
+
+        // Turn to move the canvas when the crop box is equal to the container
+        this.$face.data(DATA_ACTION, (cropBox.width === containerWidth && cropBox.height === containerHeight) ? ACTION_MOVE : ACTION_ALL);
+      }
+
+      this.$cropBox.css({
+        width: cropBox.width,
+        height: cropBox.height,
+        left: cropBox.left,
+        top: cropBox.top
+      });
+
+      if (this.isCropped && this.isLimited) {
+        this.limitCanvas(true, true);
+      }
+
+      if (!this.isDisabled) {
+        this.output();
+      }
+    },
+
+    output: function () {
+      this.preview();
+
+      if (this.isCompleted) {
+        this.trigger(EVENT_CROP, this.getData());
+      } else if (!this.isBuilt) {
+
+        // Only trigger one crop event before complete
+        this.$element.one(EVENT_BUILT, $.proxy(function () {
+          this.trigger(EVENT_CROP, this.getData());
+        }, this));
+      }
+    },
+
+    initPreview: function () {
+      var crossOrigin = getCrossOrigin(this.crossOrigin);
+      var url = crossOrigin ? this.crossOriginUrl : this.url;
+
+      this.$preview = $(this.options.preview);
+      this.$viewBox.html('<img' + crossOrigin + ' src="' + url + '">');
+      this.$preview.each(function () {
+        var $this = $(this);
+
+        // Save the original size for recover
+        $this.data(DATA_PREVIEW, {
+          width: $this.width(),
+          height: $this.height(),
+          html: $this.html()
+        });
+
+        /**
+         * Override img element styles
+         * Add `display:block` to avoid margin top issue
+         * (Occur only when margin-top <= -height)
+         */
+        $this.html(
+          '<img' + crossOrigin + ' src="' + url + '" style="' +
+          'display:block;width:100%;height:auto;' +
+          'min-width:0!important;min-height:0!important;' +
+          'max-width:none!important;max-height:none!important;' +
+          'image-orientation:0deg!important;">'
+        );
+      });
+    },
+
+    resetPreview: function () {
+      this.$preview.each(function () {
+        var $this = $(this);
+        var data = $this.data(DATA_PREVIEW);
+
+        $this.css({
+          width: data.width,
+          height: data.height
+        }).html(data.html).removeData(DATA_PREVIEW);
+      });
+    },
+
+    preview: function () {
+      var image = this.image;
+      var canvas = this.canvas;
+      var cropBox = this.cropBox;
+      var cropBoxWidth = cropBox.width;
+      var cropBoxHeight = cropBox.height;
+      var width = image.width;
+      var height = image.height;
+      var left = cropBox.left - canvas.left - image.left;
+      var top = cropBox.top - canvas.top - image.top;
+
+      if (!this.isCropped || this.isDisabled) {
+        return;
+      }
+
+      this.$viewBox.find('img').css({
+        width: width,
+        height: height,
+        marginLeft: -left,
+        marginTop: -top,
+        transform: getTransform(image)
+      });
+
+      this.$preview.each(function () {
+        var $this = $(this);
+        var data = $this.data(DATA_PREVIEW);
+        var originalWidth = data.width;
+        var originalHeight = data.height;
+        var newWidth = originalWidth;
+        var newHeight = originalHeight;
+        var ratio = 1;
+
+        if (cropBoxWidth) {
+          ratio = originalWidth / cropBoxWidth;
+          newHeight = cropBoxHeight * ratio;
+        }
+
+        if (cropBoxHeight && newHeight > originalHeight) {
+          ratio = originalHeight / cropBoxHeight;
+          newWidth = cropBoxWidth * ratio;
+          newHeight = originalHeight;
+        }
+
+        $this.css({
+          width: newWidth,
+          height: newHeight
+        }).find('img').css({
+          width: width * ratio,
+          height: height * ratio,
+          marginLeft: -left * ratio,
+          marginTop: -top * ratio,
+          transform: getTransform(image)
+        });
+      });
+    },
+
+    bind: function () {
+      var options = this.options;
+      var $this = this.$element;
+      var $cropper = this.$cropper;
+
+      if ($.isFunction(options.cropstart)) {
+        $this.on(EVENT_CROP_START, options.cropstart);
+      }
+
+      if ($.isFunction(options.cropmove)) {
+        $this.on(EVENT_CROP_MOVE, options.cropmove);
+      }
+
+      if ($.isFunction(options.cropend)) {
+        $this.on(EVENT_CROP_END, options.cropend);
+      }
+
+      if ($.isFunction(options.crop)) {
+        $this.on(EVENT_CROP, options.crop);
+      }
+
+      if ($.isFunction(options.zoom)) {
+        $this.on(EVENT_ZOOM, options.zoom);
+      }
+
+      $cropper.on(EVENT_MOUSE_DOWN, $.proxy(this.cropStart, this));
+
+      if (options.zoomable && options.zoomOnWheel) {
+        $cropper.on(EVENT_WHEEL, $.proxy(this.wheel, this));
+      }
+
+      if (options.toggleDragModeOnDblclick) {
+        $cropper.on(EVENT_DBLCLICK, $.proxy(this.dblclick, this));
+      }
+
+      $document.
+        on(EVENT_MOUSE_MOVE, (this._cropMove = proxy(this.cropMove, this))).
+        on(EVENT_MOUSE_UP, (this._cropEnd = proxy(this.cropEnd, this)));
+
+      if (options.responsive) {
+        $window.on(EVENT_RESIZE, (this._resize = proxy(this.resize, this)));
+      }
+    },
+
+    unbind: function () {
+      var options = this.options;
+      var $this = this.$element;
+      var $cropper = this.$cropper;
+
+      if ($.isFunction(options.cropstart)) {
+        $this.off(EVENT_CROP_START, options.cropstart);
+      }
+
+      if ($.isFunction(options.cropmove)) {
+        $this.off(EVENT_CROP_MOVE, options.cropmove);
+      }
+
+      if ($.isFunction(options.cropend)) {
+        $this.off(EVENT_CROP_END, options.cropend);
+      }
+
+      if ($.isFunction(options.crop)) {
+        $this.off(EVENT_CROP, options.crop);
+      }
+
+      if ($.isFunction(options.zoom)) {
+        $this.off(EVENT_ZOOM, options.zoom);
+      }
+
+      $cropper.off(EVENT_MOUSE_DOWN, this.cropStart);
+
+      if (options.zoomable && options.zoomOnWheel) {
+        $cropper.off(EVENT_WHEEL, this.wheel);
+      }
+
+      if (options.toggleDragModeOnDblclick) {
+        $cropper.off(EVENT_DBLCLICK, this.dblclick);
+      }
+
+      $document.
+        off(EVENT_MOUSE_MOVE, this._cropMove).
+        off(EVENT_MOUSE_UP, this._cropEnd);
+
+      if (options.responsive) {
+        $window.off(EVENT_RESIZE, this._resize);
+      }
+    },
+
+    resize: function () {
+      var restore = this.options.restore;
+      var $container = this.$container;
+      var container = this.container;
+      var canvasData;
+      var cropBoxData;
+      var ratio;
+
+      // Check `container` is necessary for IE8
+      if (this.isDisabled || !container) {
+        return;
+      }
+
+      ratio = $container.width() / container.width;
+
+      // Resize when width changed or height changed
+      if (ratio !== 1 || $container.height() !== container.height) {
+        if (restore) {
+          canvasData = this.getCanvasData();
+          cropBoxData = this.getCropBoxData();
+        }
+
+        this.render();
+
+        if (restore) {
+          this.setCanvasData($.each(canvasData, function (i, n) {
+            canvasData[i] = n * ratio;
+          }));
+          this.setCropBoxData($.each(cropBoxData, function (i, n) {
+            cropBoxData[i] = n * ratio;
+          }));
+        }
+      }
+    },
+
+    dblclick: function () {
+      if (this.isDisabled) {
+        return;
+      }
+
+      if (this.$dragBox.hasClass(CLASS_CROP)) {
+        this.setDragMode(ACTION_MOVE);
+      } else {
+        this.setDragMode(ACTION_CROP);
+      }
+    },
+
+    wheel: function (event) {
+      var e = event.originalEvent || event;
+      var ratio = num(this.options.wheelZoomRatio) || 0.1;
+      var delta = 1;
+
+      if (this.isDisabled) {
+        return;
+      }
+
+      event.preventDefault();
+
+      // Limit wheel speed to prevent zoom too fast
+      if (this.wheeling) {
+        return;
+      }
+
+      this.wheeling = true;
+
+      setTimeout($.proxy(function () {
+        this.wheeling = false;
+      }, this), 50);
+
+      if (e.deltaY) {
+        delta = e.deltaY > 0 ? 1 : -1;
+      } else if (e.wheelDelta) {
+        delta = -e.wheelDelta / 120;
+      } else if (e.detail) {
+        delta = e.detail > 0 ? 1 : -1;
+      }
+
+      this.zoom(-delta * ratio, event);
+    },
+
+    cropStart: function (event) {
+      var options = this.options;
+      var originalEvent = event.originalEvent;
+      var touches = originalEvent && originalEvent.touches;
+      var e = event;
+      var touchesLength;
+      var action;
+
+      if (this.isDisabled) {
+        return;
+      }
+
+      if (touches) {
+        touchesLength = touches.length;
+
+        if (touchesLength > 1) {
+          if (options.zoomable && options.zoomOnTouch && touchesLength === 2) {
+            e = touches[1];
+            this.startX2 = e.pageX;
+            this.startY2 = e.pageY;
+            action = ACTION_ZOOM;
+          } else {
+            return;
+          }
+        }
+
+        e = touches[0];
+      }
+
+      action = action || $(e.target).data(DATA_ACTION);
+
+      if (REGEXP_ACTIONS.test(action)) {
+        if (this.trigger(EVENT_CROP_START, {
+          originalEvent: originalEvent,
+          action: action
+        }).isDefaultPrevented()) {
+          return;
+        }
+
+        event.preventDefault();
+
+        this.action = action;
+        this.cropping = false;
+
+        // IE8  has `event.pageX/Y`, but not `event.originalEvent.pageX/Y`
+        // IE10 has `event.originalEvent.pageX/Y`, but not `event.pageX/Y`
+        this.startX = e.pageX || originalEvent && originalEvent.pageX;
+        this.startY = e.pageY || originalEvent && originalEvent.pageY;
+
+        if (action === ACTION_CROP) {
+          this.cropping = true;
+          this.$dragBox.addClass(CLASS_MODAL);
+        }
+      }
+    },
+
+    cropMove: function (event) {
+      var options = this.options;
+      var originalEvent = event.originalEvent;
+      var touches = originalEvent && originalEvent.touches;
+      var e = event;
+      var action = this.action;
+      var touchesLength;
+
+      if (this.isDisabled) {
+        return;
+      }
+
+      if (touches) {
+        touchesLength = touches.length;
+
+        if (touchesLength > 1) {
+          if (options.zoomable && options.zoomOnTouch && touchesLength === 2) {
+            e = touches[1];
+            this.endX2 = e.pageX;
+            this.endY2 = e.pageY;
+          } else {
+            return;
+          }
+        }
+
+        e = touches[0];
+      }
+
+      if (action) {
+        if (this.trigger(EVENT_CROP_MOVE, {
+          originalEvent: originalEvent,
+          action: action
+        }).isDefaultPrevented()) {
+          return;
+        }
+
+        event.preventDefault();
+
+        this.endX = e.pageX || originalEvent && originalEvent.pageX;
+        this.endY = e.pageY || originalEvent && originalEvent.pageY;
+
+        this.change(e.shiftKey, action === ACTION_ZOOM ? event : null);
+      }
+    },
+
+    cropEnd: function (event) {
+      var originalEvent = event.originalEvent;
+      var action = this.action;
+
+      if (this.isDisabled) {
+        return;
+      }
+
+      if (action) {
+        event.preventDefault();
+
+        if (this.cropping) {
+          this.cropping = false;
+          this.$dragBox.toggleClass(CLASS_MODAL, this.isCropped && this.options.modal);
+        }
+
+        this.action = '';
+
+        this.trigger(EVENT_CROP_END, {
+          originalEvent: originalEvent,
+          action: action
+        });
+      }
+    },
+
+    change: function (shiftKey, event) {
+      var options = this.options;
+      var aspectRatio = options.aspectRatio;
+      var action = this.action;
+      var container = this.container;
+      var canvas = this.canvas;
+      var cropBox = this.cropBox;
+      var width = cropBox.width;
+      var height = cropBox.height;
+      var left = cropBox.left;
+      var top = cropBox.top;
+      var right = left + width;
+      var bottom = top + height;
+      var minLeft = 0;
+      var minTop = 0;
+      var maxWidth = container.width;
+      var maxHeight = container.height;
+      var renderable = true;
+      var offset;
+      var range;
+
+      // Locking aspect ratio in "free mode" by holding shift key (#259)
+      if (!aspectRatio && shiftKey) {
+        aspectRatio = width && height ? width / height : 1;
+      }
+
+      if (this.limited) {
+        minLeft = cropBox.minLeft;
+        minTop = cropBox.minTop;
+        maxWidth = minLeft + min(container.width, canvas.width);
+        maxHeight = minTop + min(container.height, canvas.height);
+      }
+
+      range = {
+        x: this.endX - this.startX,
+        y: this.endY - this.startY
+      };
+
+      if (aspectRatio) {
+        range.X = range.y * aspectRatio;
+        range.Y = range.x / aspectRatio;
+      }
+
+      switch (action) {
+        // Move crop box
+        case ACTION_ALL:
+          left += range.x;
+          top += range.y;
+          break;
+
+        // Resize crop box
+        case ACTION_EAST:
+          if (range.x >= 0 && (right >= maxWidth || aspectRatio &&
+            (top <= minTop || bottom >= maxHeight))) {
+
+            renderable = false;
+            break;
+          }
+
+          width += range.x;
+
+          if (aspectRatio) {
+            height = width / aspectRatio;
+            top -= range.Y / 2;
+          }
+
+          if (width < 0) {
+            action = ACTION_WEST;
+            width = 0;
+          }
+
+          break;
+
+        case ACTION_NORTH:
+          if (range.y <= 0 && (top <= minTop || aspectRatio &&
+            (left <= minLeft || right >= maxWidth))) {
+
+            renderable = false;
+            break;
+          }
+
+          height -= range.y;
+          top += range.y;
+
+          if (aspectRatio) {
+            width = height * aspectRatio;
+            left += range.X / 2;
+          }
+
+          if (height < 0) {
+            action = ACTION_SOUTH;
+            height = 0;
+          }
+
+          break;
+
+        case ACTION_WEST:
+          if (range.x <= 0 && (left <= minLeft || aspectRatio &&
+            (top <= minTop || bottom >= maxHeight))) {
+
+            renderable = false;
+            break;
+          }
+
+          width -= range.x;
+          left += range.x;
+
+          if (aspectRatio) {
+            height = width / aspectRatio;
+            top += range.Y / 2;
+          }
+
+          if (width < 0) {
+            action = ACTION_EAST;
+            width = 0;
+          }
+
+          break;
+
+        case ACTION_SOUTH:
+          if (range.y >= 0 && (bottom >= maxHeight || aspectRatio &&
+            (left <= minLeft || right >= maxWidth))) {
+
+            renderable = false;
+            break;
+          }
+
+          height += range.y;
+
+          if (aspectRatio) {
+            width = height * aspectRatio;
+            left -= range.X / 2;
+          }
+
+          if (height < 0) {
+            action = ACTION_NORTH;
+            height = 0;
+          }
+
+          break;
+
+        case ACTION_NORTH_EAST:
+          if (aspectRatio) {
+            if (range.y <= 0 && (top <= minTop || right >= maxWidth)) {
+              renderable = false;
+              break;
+            }
+
+            height -= range.y;
+            top += range.y;
+            width = height * aspectRatio;
+          } else {
+            if (range.x >= 0) {
+              if (right < maxWidth) {
+                width += range.x;
+              } else if (range.y <= 0 && top <= minTop) {
+                renderable = false;
+              }
+            } else {
+              width += range.x;
+            }
+
+            if (range.y <= 0) {
+              if (top > minTop) {
+                height -= range.y;
+                top += range.y;
+              }
+            } else {
+              height -= range.y;
+              top += range.y;
+            }
+          }
+
+          if (width < 0 && height < 0) {
+            action = ACTION_SOUTH_WEST;
+            height = 0;
+            width = 0;
+          } else if (width < 0) {
+            action = ACTION_NORTH_WEST;
+            width = 0;
+          } else if (height < 0) {
+            action = ACTION_SOUTH_EAST;
+            height = 0;
+          }
+
+          break;
+
+        case ACTION_NORTH_WEST:
+          if (aspectRatio) {
+            if (range.y <= 0 && (top <= minTop || left <= minLeft)) {
+              renderable = false;
+              break;
+            }
+
+            height -= range.y;
+            top += range.y;
+            width = height * aspectRatio;
+            left += range.X;
+          } else {
+            if (range.x <= 0) {
+              if (left > minLeft) {
+                width -= range.x;
+                left += range.x;
+              } else if (range.y <= 0 && top <= minTop) {
+                renderable = false;
+              }
+            } else {
+              width -= range.x;
+              left += range.x;
+            }
+
+            if (range.y <= 0) {
+              if (top > minTop) {
+                height -= range.y;
+                top += range.y;
+              }
+            } else {
+              height -= range.y;
+              top += range.y;
+            }
+          }
+
+          if (width < 0 && height < 0) {
+            action = ACTION_SOUTH_EAST;
+            height = 0;
+            width = 0;
+          } else if (width < 0) {
+            action = ACTION_NORTH_EAST;
+            width = 0;
+          } else if (height < 0) {
+            action = ACTION_SOUTH_WEST;
+            height = 0;
+          }
+
+          break;
+
+        case ACTION_SOUTH_WEST:
+          if (aspectRatio) {
+            if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) {
+              renderable = false;
+              break;
+            }
+
+            width -= range.x;
+            left += range.x;
+            height = width / aspectRatio;
+          } else {
+            if (range.x <= 0) {
+              if (left > minLeft) {
+                width -= range.x;
+                left += range.x;
+              } else if (range.y >= 0 && bottom >= maxHeight) {
+                renderable = false;
+              }
+            } else {
+              width -= range.x;
+              left += range.x;
+            }
+
+            if (range.y >= 0) {
+              if (bottom < maxHeight) {
+                height += range.y;
+              }
+            } else {
+              height += range.y;
+            }
+          }
+
+          if (width < 0 && height < 0) {
+            action = ACTION_NORTH_EAST;
+            height = 0;
+            width = 0;
+          } else if (width < 0) {
+            action = ACTION_SOUTH_EAST;
+            width = 0;
+          } else if (height < 0) {
+            action = ACTION_NORTH_WEST;
+            height = 0;
+          }
+
+          break;
+
+        case ACTION_SOUTH_EAST:
+          if (aspectRatio) {
+            if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
+              renderable = false;
+              break;
+            }
+
+            width += range.x;
+            height = width / aspectRatio;
+          } else {
+            if (range.x >= 0) {
+              if (right < maxWidth) {
+                width += range.x;
+              } else if (range.y >= 0 && bottom >= maxHeight) {
+                renderable = false;
+              }
+            } else {
+              width += range.x;
+            }
+
+            if (range.y >= 0) {
+              if (bottom < maxHeight) {
+                height += range.y;
+              }
+            } else {
+              height += range.y;
+            }
+          }
+
+          if (width < 0 && height < 0) {
+            action = ACTION_NORTH_WEST;
+            height = 0;
+            width = 0;
+          } else if (width < 0) {
+            action = ACTION_SOUTH_WEST;
+            width = 0;
+          } else if (height < 0) {
+            action = ACTION_NORTH_EAST;
+            height = 0;
+          }
+
+          break;
+
+        // Move canvas
+        case ACTION_MOVE:
+          this.move(range.x, range.y);
+          renderable = false;
+          break;
+
+        // Zoom canvas
+        case ACTION_ZOOM:
+          this.zoom((function (x1, y1, x2, y2) {
+            var z1 = sqrt(x1 * x1 + y1 * y1);
+            var z2 = sqrt(x2 * x2 + y2 * y2);
+
+            return (z2 - z1) / z1;
+          })(
+            abs(this.startX - this.startX2),
+            abs(this.startY - this.startY2),
+            abs(this.endX - this.endX2),
+            abs(this.endY - this.endY2)
+          ), event);
+          this.startX2 = this.endX2;
+          this.startY2 = this.endY2;
+          renderable = false;
+          break;
+
+        // Create crop box
+        case ACTION_CROP:
+          if (!range.x || !range.y) {
+            renderable = false;
+            break;
+          }
+
+          offset = this.$cropper.offset();
+          left = this.startX - offset.left;
+          top = this.startY - offset.top;
+          width = cropBox.minWidth;
+          height = cropBox.minHeight;
+
+          if (range.x > 0) {
+            action = range.y > 0 ? ACTION_SOUTH_EAST : ACTION_NORTH_EAST;
+          } else if (range.x < 0) {
+            left -= width;
+            action = range.y > 0 ? ACTION_SOUTH_WEST : ACTION_NORTH_WEST;
+          }
+
+          if (range.y < 0) {
+            top -= height;
+          }
+
+          // Show the crop box if is hidden
+          if (!this.isCropped) {
+            this.$cropBox.removeClass(CLASS_HIDDEN);
+            this.isCropped = true;
+
+            if (this.limited) {
+              this.limitCropBox(true, true);
+            }
+          }
+
+          break;
+
+        // No default
+      }
+
+      if (renderable) {
+        cropBox.width = width;
+        cropBox.height = height;
+        cropBox.left = left;
+        cropBox.top = top;
+        this.action = action;
+
+        this.renderCropBox();
+      }
+
+      // Override
+      this.startX = this.endX;
+      this.startY = this.endY;
+    },
+
+    // Show the crop box manually
+    crop: function () {
+      if (!this.isBuilt || this.isDisabled) {
+        return;
+      }
+
+      if (!this.isCropped) {
+        this.isCropped = true;
+        this.limitCropBox(true, true);
+
+        if (this.options.modal) {
+          this.$dragBox.addClass(CLASS_MODAL);
+        }
+
+        this.$cropBox.removeClass(CLASS_HIDDEN);
+      }
+
+      this.setCropBoxData(this.initialCropBox);
+    },
+
+    // Reset the image and crop box to their initial states
+    reset: function () {
+      if (!this.isBuilt || this.isDisabled) {
+        return;
+      }
+
+      this.image = $.extend({}, this.initialImage);
+      this.canvas = $.extend({}, this.initialCanvas);
+      this.cropBox = $.extend({}, this.initialCropBox);
+
+      this.renderCanvas();
+
+      if (this.isCropped) {
+        this.renderCropBox();
+      }
+    },
+
+    // Clear the crop box
+    clear: function () {
+      if (!this.isCropped || this.isDisabled) {
+        return;
+      }
+
+      $.extend(this.cropBox, {
+        left: 0,
+        top: 0,
+        width: 0,
+        height: 0
+      });
+
+      this.isCropped = false;
+      this.renderCropBox();
+
+      this.limitCanvas(true, true);
+
+      // Render canvas after crop box rendered
+      this.renderCanvas();
+
+      this.$dragBox.removeClass(CLASS_MODAL);
+      this.$cropBox.addClass(CLASS_HIDDEN);
+    },
+
+    /**
+     * Replace the image's src and rebuild the cropper
+     *
+     * @param {String} url
+     */
+    replace: function (url) {
+      if (!this.isDisabled && url) {
+        if (this.isImg) {
+          this.isReplaced = true;
+          this.$element.attr('src', url);
+        }
+
+        // Clear previous data
+        this.options.data = null;
+        this.load(url);
+      }
+    },
+
+    // Enable (unfreeze) the cropper
+    enable: function () {
+      if (this.isBuilt) {
+        this.isDisabled = false;
+        this.$cropper.removeClass(CLASS_DISABLED);
+      }
+    },
+
+    // Disable (freeze) the cropper
+    disable: function () {
+      if (this.isBuilt) {
+        this.isDisabled = true;
+        this.$cropper.addClass(CLASS_DISABLED);
+      }
+    },
+
+    // Destroy the cropper and remove the instance from the image
+    destroy: function () {
+      var $this = this.$element;
+
+      if (this.isLoaded) {
+        if (this.isImg && this.isReplaced) {
+          $this.attr('src', this.originalUrl);
+        }
+
+        this.unbuild();
+        $this.removeClass(CLASS_HIDDEN);
+      } else {
+        if (this.isImg) {
+          $this.off(EVENT_LOAD, this.start);
+        } else if (this.$clone) {
+          this.$clone.remove();
+        }
+      }
+
+      $this.removeData(NAMESPACE);
+    },
+
+    /**
+     * Move the canvas with relative offsets
+     *
+     * @param {Number} offsetX
+     * @param {Number} offsetY (optional)
+     */
+    move: function (offsetX, offsetY) {
+      var canvas = this.canvas;
+
+      this.moveTo(
+        isUndefined(offsetX) ? offsetX : canvas.left + num(offsetX),
+        isUndefined(offsetY) ? offsetY : canvas.top + num(offsetY)
+      );
+    },
+
+    /**
+     * Move the canvas to an absolute point
+     *
+     * @param {Number} x
+     * @param {Number} y (optional)
+     */
+    moveTo: function (x, y) {
+      var canvas = this.canvas;
+      var isChanged = false;
+
+      // If "y" is not present, its default value is "x"
+      if (isUndefined(y)) {
+        y = x;
+      }
+
+      x = num(x);
+      y = num(y);
+
+      if (this.isBuilt && !this.isDisabled && this.options.movable) {
+        if (isNumber(x)) {
+          canvas.left = x;
+          isChanged = true;
+        }
+
+        if (isNumber(y)) {
+          canvas.top = y;
+          isChanged = true;
+        }
+
+        if (isChanged) {
+          this.renderCanvas(true);
+        }
+      }
+    },
+
+    /**
+     * Zoom the canvas with a relative ratio
+     *
+     * @param {Number} ratio
+     * @param {jQuery Event} _event (private)
+     */
+    zoom: function (ratio, _event) {
+      var canvas = this.canvas;
+
+      ratio = num(ratio);
+
+      if (ratio < 0) {
+        ratio =  1 / (1 - ratio);
+      } else {
+        ratio = 1 + ratio;
+      }
+
+      this.zoomTo(canvas.width * ratio / canvas.naturalWidth, _event);
+    },
+
+    /**
+     * Zoom the canvas to an absolute ratio
+     *
+     * @param {Number} ratio
+     * @param {jQuery Event} _event (private)
+     */
+    zoomTo: function (ratio, _event) {
+      var options = this.options;
+      var canvas = this.canvas;
+      var width = canvas.width;
+      var height = canvas.height;
+      var naturalWidth = canvas.naturalWidth;
+      var naturalHeight = canvas.naturalHeight;
+      var originalEvent;
+      var newWidth;
+      var newHeight;
+      var offset;
+      var center;
+
+      ratio = num(ratio);
+
+      if (ratio >= 0 && this.isBuilt && !this.isDisabled && options.zoomable) {
+        newWidth = naturalWidth * ratio;
+        newHeight = naturalHeight * ratio;
+
+        if (_event) {
+          originalEvent = _event.originalEvent;
+        }
+
+        if (this.trigger(EVENT_ZOOM, {
+          originalEvent: originalEvent,
+          oldRatio: width / naturalWidth,
+          ratio: newWidth / naturalWidth
+        }).isDefaultPrevented()) {
+          return;
+        }
+
+        if (originalEvent) {
+          offset = this.$cropper.offset();
+          center = originalEvent.touches ? getTouchesCenter(originalEvent.touches) : {
+            pageX: _event.pageX || originalEvent.pageX || 0,
+            pageY: _event.pageY || originalEvent.pageY || 0
+          };
+
+          // Zoom from the triggering point of the event
+          canvas.left -= (newWidth - width) * (
+            ((center.pageX - offset.left) - canvas.left) / width
+          );
+          canvas.top -= (newHeight - height) * (
+            ((center.pageY - offset.top) - canvas.top) / height
+          );
+        } else {
+
+          // Zoom from the center of the canvas
+          canvas.left -= (newWidth - width) / 2;
+          canvas.top -= (newHeight - height) / 2;
+        }
+
+        canvas.width = newWidth;
+        canvas.height = newHeight;
+        this.renderCanvas(true);
+      }
+    },
+
+    /**
+     * Rotate the canvas with a relative degree
+     *
+     * @param {Number} degree
+     */
+    rotate: function (degree) {
+      this.rotateTo((this.image.rotate || 0) + num(degree));
+    },
+
+    /**
+     * Rotate the canvas to an absolute degree
+     * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#rotate()
+     *
+     * @param {Number} degree
+     */
+    rotateTo: function (degree) {
+      degree = num(degree);
+
+      if (isNumber(degree) && this.isBuilt && !this.isDisabled && this.options.rotatable) {
+        this.image.rotate = degree % 360;
+        this.isRotated = true;
+        this.renderCanvas(true);
+      }
+    },
+
+    /**
+     * Scale the image
+     * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#scale()
+     *
+     * @param {Number} scaleX
+     * @param {Number} scaleY (optional)
+     */
+    scale: function (scaleX, scaleY) {
+      var image = this.image;
+      var isChanged = false;
+
+      // If "scaleY" is not present, its default value is "scaleX"
+      if (isUndefined(scaleY)) {
+        scaleY = scaleX;
+      }
+
+      scaleX = num(scaleX);
+      scaleY = num(scaleY);
+
+      if (this.isBuilt && !this.isDisabled && this.options.scalable) {
+        if (isNumber(scaleX)) {
+          image.scaleX = scaleX;
+          isChanged = true;
+        }
+
+        if (isNumber(scaleY)) {
+          image.scaleY = scaleY;
+          isChanged = true;
+        }
+
+        if (isChanged) {
+          this.renderImage(true);
+        }
+      }
+    },
+
+    /**
+     * Scale the abscissa of the image
+     *
+     * @param {Number} scaleX
+     */
+    scaleX: function (scaleX) {
+      var scaleY = this.image.scaleY;
+
+      this.scale(scaleX, isNumber(scaleY) ? scaleY : 1);
+    },
+
+    /**
+     * Scale the ordinate of the image
+     *
+     * @param {Number} scaleY
+     */
+    scaleY: function (scaleY) {
+      var scaleX = this.image.scaleX;
+
+      this.scale(isNumber(scaleX) ? scaleX : 1, scaleY);
+    },
+
+    /**
+     * Get the cropped area position and size data (base on the original image)
+     *
+     * @param {Boolean} isRounded (optional)
+     * @return {Object} data
+     */
+    getData: function (isRounded) {
+      var options = this.options;
+      var image = this.image;
+      var canvas = this.canvas;
+      var cropBox = this.cropBox;
+      var ratio;
+      var data;
+
+      if (this.isBuilt && this.isCropped) {
+        data = {
+          x: cropBox.left - canvas.left,
+          y: cropBox.top - canvas.top,
+          width: cropBox.width,
+          height: cropBox.height
+        };
+
+        ratio = image.width / image.naturalWidth;
+
+        $.each(data, function (i, n) {
+          n = n / ratio;
+          data[i] = isRounded ? round(n) : n;
+        });
+
+      } else {
+        data = {
+          x: 0,
+          y: 0,
+          width: 0,
+          height: 0
+        };
+      }
+
+      if (options.rotatable) {
+        data.rotate = image.rotate || 0;
+      }
+
+      if (options.scalable) {
+        data.scaleX = image.scaleX || 1;
+        data.scaleY = image.scaleY || 1;
+      }
+
+      return data;
+    },
+
+    /**
+     * Set the cropped area position and size with new data
+     *
+     * @param {Object} data
+     */
+    setData: function (data) {
+      var options = this.options;
+      var image = this.image;
+      var canvas = this.canvas;
+      var cropBoxData = {};
+      var isRotated;
+      var isScaled;
+      var ratio;
+
+      if ($.isFunction(data)) {
+        data = data.call(this.element);
+      }
+
+      if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) {
+        if (options.rotatable) {
+          if (isNumber(data.rotate) && data.rotate !== image.rotate) {
+            image.rotate = data.rotate;
+            this.isRotated = isRotated = true;
+          }
+        }
+
+        if (options.scalable) {
+          if (isNumber(data.scaleX) && data.scaleX !== image.scaleX) {
+            image.scaleX = data.scaleX;
+            isScaled = true;
+          }
+
+          if (isNumber(data.scaleY) && data.scaleY !== image.scaleY) {
+            image.scaleY = data.scaleY;
+            isScaled = true;
+          }
+        }
+
+        if (isRotated) {
+          this.renderCanvas();
+        } else if (isScaled) {
+          this.renderImage();
+        }
+
+        ratio = image.width / image.naturalWidth;
+
+        if (isNumber(data.x)) {
+          cropBoxData.left = data.x * ratio + canvas.left;
+        }
+
+        if (isNumber(data.y)) {
+          cropBoxData.top = data.y * ratio + canvas.top;
+        }
+
+        if (isNumber(data.width)) {
+          cropBoxData.width = data.width * ratio;
+        }
+
+        if (isNumber(data.height)) {
+          cropBoxData.height = data.height * ratio;
+        }
+
+        this.setCropBoxData(cropBoxData);
+      }
+    },
+
+    /**
+     * Get the container size data
+     *
+     * @return {Object} data
+     */
+    getContainerData: function () {
+      return this.isBuilt ? this.container : {};
+    },
+
+    /**
+     * Get the image position and size data
+     *
+     * @return {Object} data
+     */
+    getImageData: function () {
+      return this.isLoaded ? this.image : {};
+    },
+
+    /**
+     * Get the canvas position and size data
+     *
+     * @return {Object} data
+     */
+    getCanvasData: function () {
+      var canvas = this.canvas;
+      var data = {};
+
+      if (this.isBuilt) {
+        $.each([
+          'left',
+          'top',
+          'width',
+          'height',
+          'naturalWidth',
+          'naturalHeight'
+        ], function (i, n) {
+          data[n] = canvas[n];
+        });
+      }
+
+      return data;
+    },
+
+    /**
+     * Set the canvas position and size with new data
+     *
+     * @param {Object} data
+     */
+    setCanvasData: function (data) {
+      var canvas = this.canvas;
+      var aspectRatio = canvas.aspectRatio;
+
+      if ($.isFunction(data)) {
+        data = data.call(this.$element);
+      }
+
+      if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) {
+        if (isNumber(data.left)) {
+          canvas.left = data.left;
+        }
+
+        if (isNumber(data.top)) {
+          canvas.top = data.top;
+        }
+
+        if (isNumber(data.width)) {
+          canvas.width = data.width;
+          canvas.height = data.width / aspectRatio;
+        } else if (isNumber(data.height)) {
+          canvas.height = data.height;
+          canvas.width = data.height * aspectRatio;
+        }
+
+        this.renderCanvas(true);
+      }
+    },
+
+    /**
+     * Get the crop box position and size data
+     *
+     * @return {Object} data
+     */
+    getCropBoxData: function () {
+      var cropBox = this.cropBox;
+      var data;
+
+      if (this.isBuilt && this.isCropped) {
+        data = {
+          left: cropBox.left,
+          top: cropBox.top,
+          width: cropBox.width,
+          height: cropBox.height
+        };
+      }
+
+      return data || {};
+    },
+
+    /**
+     * Set the crop box position and size with new data
+     *
+     * @param {Object} data
+     */
+    setCropBoxData: function (data) {
+      var cropBox = this.cropBox;
+      var aspectRatio = this.options.aspectRatio;
+      var isWidthChanged;
+      var isHeightChanged;
+
+      if ($.isFunction(data)) {
+        data = data.call(this.$element);
+      }
+
+      if (this.isBuilt && this.isCropped && !this.isDisabled && $.isPlainObject(data)) {
+
+        if (isNumber(data.left)) {
+          cropBox.left = data.left;
+        }
+
+        if (isNumber(data.top)) {
+          cropBox.top = data.top;
+        }
+
+        if (isNumber(data.width)) {
+          isWidthChanged = true;
+          cropBox.width = data.width;
+        }
+
+        if (isNumber(data.height)) {
+          isHeightChanged = true;
+          cropBox.height = data.height;
+        }
+
+        if (aspectRatio) {
+          if (isWidthChanged) {
+            cropBox.height = cropBox.width / aspectRatio;
+          } else if (isHeightChanged) {
+            cropBox.width = cropBox.height * aspectRatio;
+          }
+        }
+
+        this.renderCropBox();
+      }
+    },
+
+    /**
+     * Get a canvas drawn the cropped image
+     *
+     * @param {Object} options (optional)
+     * @return {HTMLCanvasElement} canvas
+     */
+    getCroppedCanvas: function (options) {
+      var originalWidth;
+      var originalHeight;
+      var canvasWidth;
+      var canvasHeight;
+      var scaledWidth;
+      var scaledHeight;
+      var scaledRatio;
+      var aspectRatio;
+      var canvas;
+      var context;
+      var data;
+
+      if (!this.isBuilt || !this.isCropped || !SUPPORT_CANVAS) {
+        return;
+      }
+
+      if (!$.isPlainObject(options)) {
+        options = {};
+      }
+
+      data = this.getData();
+      originalWidth = data.width;
+      originalHeight = data.height;
+      aspectRatio = originalWidth / originalHeight;
+
+      if ($.isPlainObject(options)) {
+        scaledWidth = options.width;
+        scaledHeight = options.height;
+
+        if (scaledWidth) {
+          scaledHeight = scaledWidth / aspectRatio;
+          scaledRatio = scaledWidth / originalWidth;
+        } else if (scaledHeight) {
+          scaledWidth = scaledHeight * aspectRatio;
+          scaledRatio = scaledHeight / originalHeight;
+        }
+      }
+
+      // The canvas element will use `Math.floor` on a float number, so floor first
+      canvasWidth = floor(scaledWidth || originalWidth);
+      canvasHeight = floor(scaledHeight || originalHeight);
+
+      canvas = $('<canvas>')[0];
+      canvas.width = canvasWidth;
+      canvas.height = canvasHeight;
+      context = canvas.getContext('2d');
+
+      if (options.fillColor) {
+        context.fillStyle = options.fillColor;
+        context.fillRect(0, 0, canvasWidth, canvasHeight);
+      }
+
+      // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage
+      context.drawImage.apply(context, (function () {
+        var source = getSourceCanvas(this.$clone[0], this.image);
+        var sourceWidth = source.width;
+        var sourceHeight = source.height;
+        var args = [source];
+
+        // Source canvas
+        var srcX = data.x;
+        var srcY = data.y;
+        var srcWidth;
+        var srcHeight;
+
+        // Destination canvas
+        var dstX;
+        var dstY;
+        var dstWidth;
+        var dstHeight;
+
+        if (srcX <= -originalWidth || srcX > sourceWidth) {
+          srcX = srcWidth = dstX = dstWidth = 0;
+        } else if (srcX <= 0) {
+          dstX = -srcX;
+          srcX = 0;
+          srcWidth = dstWidth = min(sourceWidth, originalWidth + srcX);
+        } else if (srcX <= sourceWidth) {
+          dstX = 0;
+          srcWidth = dstWidth = min(originalWidth, sourceWidth - srcX);
+        }
+
+        if (srcWidth <= 0 || srcY <= -originalHeight || srcY > sourceHeight) {
+          srcY = srcHeight = dstY = dstHeight = 0;
+        } else if (srcY <= 0) {
+          dstY = -srcY;
+          srcY = 0;
+          srcHeight = dstHeight = min(sourceHeight, originalHeight + srcY);
+        } else if (srcY <= sourceHeight) {
+          dstY = 0;
+          srcHeight = dstHeight = min(originalHeight, sourceHeight - srcY);
+        }
+
+        // All the numerical parameters should be integer for `drawImage` (#476)
+        args.push(floor(srcX), floor(srcY), floor(srcWidth), floor(srcHeight));
+
+        // Scale destination sizes
+        if (scaledRatio) {
+          dstX *= scaledRatio;
+          dstY *= scaledRatio;
+          dstWidth *= scaledRatio;
+          dstHeight *= scaledRatio;
+        }
+
+        // Avoid "IndexSizeError" in IE and Firefox
+        if (dstWidth > 0 && dstHeight > 0) {
+          args.push(floor(dstX), floor(dstY), floor(dstWidth), floor(dstHeight));
+        }
+
+        return args;
+      }).call(this));
+
+      return canvas;
+    },
+
+    /**
+     * Change the aspect ratio of the crop box
+     *
+     * @param {Number} aspectRatio
+     */
+    setAspectRatio: function (aspectRatio) {
+      var options = this.options;
+
+      if (!this.isDisabled && !isUndefined(aspectRatio)) {
+
+        // 0 -> NaN
+        options.aspectRatio = max(0, aspectRatio) || NaN;
+
+        if (this.isBuilt) {
+          this.initCropBox();
+
+          if (this.isCropped) {
+            this.renderCropBox();
+          }
+        }
+      }
+    },
+
+    /**
+     * Change the drag mode
+     *
+     * @param {String} mode (optional)
+     */
+    setDragMode: function (mode) {
+      var options = this.options;
+      var croppable;
+      var movable;
+
+      if (this.isLoaded && !this.isDisabled) {
+        croppable = mode === ACTION_CROP;
+        movable = options.movable && mode === ACTION_MOVE;
+        mode = (croppable || movable) ? mode : ACTION_NONE;
+
+        this.$dragBox.
+          data(DATA_ACTION, mode).
+          toggleClass(CLASS_CROP, croppable).
+          toggleClass(CLASS_MOVE, movable);
+
+        if (!options.cropBoxMovable) {
+
+          // Sync drag mode to crop box when it is not movable(#300)
+          this.$face.
+            data(DATA_ACTION, mode).
+            toggleClass(CLASS_CROP, croppable).
+            toggleClass(CLASS_MOVE, movable);
+        }
+      }
+    }
+  };
+
+  Cropper.DEFAULTS = {
+
+    // Define the view mode of the cropper
+    viewMode: 0, // 0, 1, 2, 3
+
+    // Define the dragging mode of the cropper
+    dragMode: 'crop', // 'crop', 'move' or 'none'
+
+    // Define the aspect ratio of the crop box
+    aspectRatio: NaN,
+
+    // An object with the previous cropping result data
+    data: null,
+
+    // A jQuery selector for adding extra containers to preview
+    preview: '',
+
+    // Re-render the cropper when resize the window
+    responsive: true,
+
+    // Restore the cropped area after resize the window
+    restore: true,
+
+    // Check if the current image is a cross-origin image
+    checkCrossOrigin: true,
+
+    // Check the current image's Exif Orientation information
+    checkOrientation: true,
+
+    // Show the black modal
+    modal: true,
+
+    // Show the dashed lines for guiding
+    guides: true,
+
+    // Show the center indicator for guiding
+    center: true,
+
+    // Show the white modal to highlight the crop box
+    highlight: true,
+
+    // Show the grid background
+    background: true,
+
+    // Enable to crop the image automatically when initialize
+    autoCrop: true,
+
+    // Define the percentage of automatic cropping area when initializes
+    autoCropArea: 0.8,
+
+    // Enable to move the image
+    movable: true,
+
+    // Enable to rotate the image
+    rotatable: true,
+
+    // Enable to scale the image
+    scalable: true,
+
+    // Enable to zoom the image
+    zoomable: true,
+
+    // Enable to zoom the image by dragging touch
+    zoomOnTouch: true,
+
+    // Enable to zoom the image by wheeling mouse
+    zoomOnWheel: true,
+
+    // Define zoom ratio when zoom the image by wheeling mouse
+    wheelZoomRatio: 0.1,
+
+    // Enable to move the crop box
+    cropBoxMovable: true,
+
+    // Enable to resize the crop box
+    cropBoxResizable: true,
+
+    // Toggle drag mode between "crop" and "move" when click twice on the cropper
+    toggleDragModeOnDblclick: true,
+
+    // Size limitation
+    minCanvasWidth: 0,
+    minCanvasHeight: 0,
+    minCropBoxWidth: 0,
+    minCropBoxHeight: 0,
+    minContainerWidth: 200,
+    minContainerHeight: 100,
+
+    // Shortcuts of events
+    build: null,
+    built: null,
+    cropstart: null,
+    cropmove: null,
+    cropend: null,
+    crop: null,
+    zoom: null
+  };
+
+  Cropper.setDefaults = function (options) {
+    $.extend(Cropper.DEFAULTS, options);
+  };
+
+  Cropper.TEMPLATE = (
+    '<div class="cropper-container">' +
+      '<div class="cropper-wrap-box">' +
+        '<div class="cropper-canvas"></div>' +
+      '</div>' +
+      '<div class="cropper-drag-box"></div>' +
+      '<div class="cropper-crop-box">' +
+        '<span class="cropper-view-box"></span>' +
+        '<span class="cropper-dashed dashed-h"></span>' +
+        '<span class="cropper-dashed dashed-v"></span>' +
+        '<span class="cropper-center"></span>' +
+        '<span class="cropper-face"></span>' +
+        '<span class="cropper-line line-e" data-action="e"></span>' +
+        '<span class="cropper-line line-n" data-action="n"></span>' +
+        '<span class="cropper-line line-w" data-action="w"></span>' +
+        '<span class="cropper-line line-s" data-action="s"></span>' +
+        '<span class="cropper-point point-e" data-action="e"></span>' +
+        '<span class="cropper-point point-n" data-action="n"></span>' +
+        '<span class="cropper-point point-w" data-action="w"></span>' +
+        '<span class="cropper-point point-s" data-action="s"></span>' +
+        '<span class="cropper-point point-ne" data-action="ne"></span>' +
+        '<span class="cropper-point point-nw" data-action="nw"></span>' +
+        '<span class="cropper-point point-sw" data-action="sw"></span>' +
+        '<span class="cropper-point point-se" data-action="se"></span>' +
+      '</div>' +
+    '</div>'
+  );
+
+  // Save the other cropper
+  Cropper.other = $.fn.cropper;
+
+  // Register as jQuery plugin
+  $.fn.cropper = function (option) {
+    var args = toArray(arguments, 1);
+    var result;
+
+    this.each(function () {
+      var $this = $(this);
+      var data = $this.data(NAMESPACE);
+      var options;
+      var fn;
+
+      if (!data) {
+        if (/destroy/.test(option)) {
+          return;
+        }
+
+        options = $.extend({}, $this.data(), $.isPlainObject(option) && option);
+        $this.data(NAMESPACE, (data = new Cropper(this, options)));
+      }
+
+      if (typeof option === 'string' && $.isFunction(fn = data[option])) {
+        result = fn.apply(data, args);
+      }
+    });
+
+    return isUndefined(result) ? this : result;
+  };
+
+  $.fn.cropper.Constructor = Cropper;
+  $.fn.cropper.setDefaults = Cropper.setDefaults;
+
+  // No conflict
+  $.fn.cropper.noConflict = function () {
+    $.fn.cropper = Cropper.other;
+    return this;
+  };
+
+});
diff --git a/vendor/assets/stylesheets/cropper.css b/vendor/assets/stylesheets/cropper.css
new file mode 100755
index 0000000000000000000000000000000000000000..41ee4bd546c8dd405c26b47272efe4d90f467b67
--- /dev/null
+++ b/vendor/assets/stylesheets/cropper.css
@@ -0,0 +1,379 @@
+/*!
+ * Cropper v2.2.5
+ * https://github.com/fengyuanchen/cropper
+ *
+ * Copyright (c) 2014-2016 Fengyuan Chen and contributors
+ * Released under the MIT license
+ *
+ * Date: 2016-01-18T05:42:29.639Z
+ */
+.cropper-container {
+  font-size: 0;
+  line-height: 0;
+
+  position: relative;
+
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none;
+
+  direction: ltr !important;
+  -ms-touch-action: none;
+      touch-action: none;
+  -webkit-tap-highlight-color: transparent;
+  -webkit-touch-callout: none;
+}
+
+.cropper-container img {
+  display: block;
+
+  width: 100%;
+  min-width: 0 !important;
+  max-width: none !important;
+  height: 100%;
+  min-height: 0 !important;
+  max-height: none !important;
+
+  image-orientation: 0deg !important;
+}
+
+.cropper-wrap-box,
+.cropper-canvas,
+.cropper-drag-box,
+.cropper-crop-box,
+.cropper-modal {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+}
+
+.cropper-wrap-box {
+  overflow: hidden;
+}
+
+.cropper-drag-box {
+  opacity: 0;
+  background-color: #fff;
+
+  filter: alpha(opacity=0);
+}
+
+.cropper-modal {
+  opacity: .5;
+  background-color: #000;
+
+  filter: alpha(opacity=50);
+}
+
+.cropper-view-box {
+  display: block;
+  overflow: hidden;
+
+  width: 100%;
+  height: 100%;
+
+  outline: 1px solid #39f;
+  outline-color: rgba(51, 153, 255, .75);
+}
+
+.cropper-dashed {
+  position: absolute;
+
+  display: block;
+
+  opacity: .5;
+  border: 0 dashed #eee;
+
+  filter: alpha(opacity=50);
+}
+
+.cropper-dashed.dashed-h {
+  top: 33.33333%;
+  left: 0;
+
+  width: 100%;
+  height: 33.33333%;
+
+  border-top-width: 1px;
+  border-bottom-width: 1px;
+}
+
+.cropper-dashed.dashed-v {
+  top: 0;
+  left: 33.33333%;
+
+  width: 33.33333%;
+  height: 100%;
+
+  border-right-width: 1px;
+  border-left-width: 1px;
+}
+
+.cropper-center {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+
+  display: block;
+
+  width: 0;
+  height: 0;
+
+  opacity: .75;
+
+  filter: alpha(opacity=75);
+}
+
+.cropper-center:before,
+.cropper-center:after {
+  position: absolute;
+
+  display: block;
+
+  content: ' ';
+
+  background-color: #eee;
+}
+
+.cropper-center:before {
+  top: 0;
+  left: -3px;
+
+  width: 7px;
+  height: 1px;
+}
+
+.cropper-center:after {
+  top: -3px;
+  left: 0;
+
+  width: 1px;
+  height: 7px;
+}
+
+.cropper-face,
+.cropper-line,
+.cropper-point {
+  position: absolute;
+
+  display: block;
+
+  width: 100%;
+  height: 100%;
+
+  opacity: .1;
+
+  filter: alpha(opacity=10);
+}
+
+.cropper-face {
+  top: 0;
+  left: 0;
+
+  background-color: #fff;
+}
+
+.cropper-line {
+  background-color: #39f;
+}
+
+.cropper-line.line-e {
+  top: 0;
+  right: -3px;
+
+  width: 5px;
+
+  cursor: e-resize;
+}
+
+.cropper-line.line-n {
+  top: -3px;
+  left: 0;
+
+  height: 5px;
+
+  cursor: n-resize;
+}
+
+.cropper-line.line-w {
+  top: 0;
+  left: -3px;
+
+  width: 5px;
+
+  cursor: w-resize;
+}
+
+.cropper-line.line-s {
+  bottom: -3px;
+  left: 0;
+
+  height: 5px;
+
+  cursor: s-resize;
+}
+
+.cropper-point {
+  width: 5px;
+  height: 5px;
+
+  opacity: .75;
+  background-color: #39f;
+
+  filter: alpha(opacity=75);
+}
+
+.cropper-point.point-e {
+  top: 50%;
+  right: -3px;
+
+  margin-top: -3px;
+
+  cursor: e-resize;
+}
+
+.cropper-point.point-n {
+  top: -3px;
+  left: 50%;
+
+  margin-left: -3px;
+
+  cursor: n-resize;
+}
+
+.cropper-point.point-w {
+  top: 50%;
+  left: -3px;
+
+  margin-top: -3px;
+
+  cursor: w-resize;
+}
+
+.cropper-point.point-s {
+  bottom: -3px;
+  left: 50%;
+
+  margin-left: -3px;
+
+  cursor: s-resize;
+}
+
+.cropper-point.point-ne {
+  top: -3px;
+  right: -3px;
+
+  cursor: ne-resize;
+}
+
+.cropper-point.point-nw {
+  top: -3px;
+  left: -3px;
+
+  cursor: nw-resize;
+}
+
+.cropper-point.point-sw {
+  bottom: -3px;
+  left: -3px;
+
+  cursor: sw-resize;
+}
+
+.cropper-point.point-se {
+  right: -3px;
+  bottom: -3px;
+
+  width: 20px;
+  height: 20px;
+
+  cursor: se-resize;
+
+  opacity: 1;
+
+  filter: alpha(opacity=100);
+}
+
+.cropper-point.point-se:before {
+  position: absolute;
+  right: -50%;
+  bottom: -50%;
+
+  display: block;
+
+  width: 200%;
+  height: 200%;
+
+  content: ' ';
+
+  opacity: 0;
+  background-color: #39f;
+
+  filter: alpha(opacity=0);
+}
+
+@media (min-width: 768px) {
+  .cropper-point.point-se {
+    width: 15px;
+    height: 15px;
+  }
+}
+
+@media (min-width: 992px) {
+  .cropper-point.point-se {
+    width: 10px;
+    height: 10px;
+  }
+}
+
+@media (min-width: 1200px) {
+  .cropper-point.point-se {
+    width: 5px;
+    height: 5px;
+
+    opacity: .75;
+
+    filter: alpha(opacity=75);
+  }
+}
+
+.cropper-invisible {
+  opacity: 0;
+
+  filter: alpha(opacity=0);
+}
+
+.cropper-bg {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
+}
+
+.cropper-hide {
+  position: absolute;
+
+  display: block;
+
+  width: 0;
+  height: 0;
+}
+
+.cropper-hidden {
+  display: none !important;
+}
+
+.cropper-move {
+  cursor: move;
+}
+
+.cropper-crop {
+  cursor: crosshair;
+}
+
+.cropper-disabled .cropper-drag-box,
+.cropper-disabled .cropper-face,
+.cropper-disabled .cropper-line,
+.cropper-disabled .cropper-point {
+  cursor: not-allowed;
+}