diff --git a/CHANGELOG b/CHANGELOG
index 163a0ea54e8e372628fcaf96162c8a86d486a3c5..31833117e4b051c5411221cd085a81ce91b26fcf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,19 +2,30 @@ Please view this file on the master branch, on stable branches it's out of date.
 
 v 8.3.0 (unreleased)
   - Merge when build succeeds (Zeger-Jan van de Weg)
+  - Fix API setting of 'public' attribute to false will make a project private (Stan Hu)
+  - Handle and report SSL errors in Web hook test (Stan Hu)
   - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
   - Fix 500 error when update group member permission
   - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
   - Recognize issue/MR/snippet/commit links as references
   - Add ignore whitespace change option to commit view
   - Fire update hook from GitLab
+  - Style warning about mentioning many people in a comment
+  - Fix: sort milestones by due date once again (Greg Smethells)
   - Don't show project fork event as "imported"
   - Add API endpoint to fetch merge request commits list
   - Expose events API with comment information and author info
   - Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583
+  - Fix 500 error when creating a merge request that removes a submodule
+  - Run custom Git hooks when branch is created or deleted.
+  - Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch
 
 v 8.2.3
   - Fix application settings cache not expiring after changes (Stan Hu)
+  - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu)
+
+v 8.2.3
+  - Webhook payload has an added, modified and removed properties for each commit
 
 v 8.2.2
   - Fix 404 in redirection after removing a project (Stan Hu)
diff --git a/Gemfile b/Gemfile
index 67640bb9ae08ad8afed8d6b44f3244b5e75c4862..fc4d565fc844e18a9149ae2ed53a71e62406c257 100644
--- a/Gemfile
+++ b/Gemfile
@@ -99,7 +99,7 @@ gem 'org-ruby',      '~> 0.9.12'
 gem 'creole',        '~> 0.5.0'
 gem 'wikicloth',     '0.8.1'
 gem 'asciidoctor',   '~> 1.5.2'
-gem 'net-ssh',       '~> 3.0.1'
+gem 'rouge',         '~> 1.10.1'
 
 # Diffs
 gem 'diffy', '~> 3.0.3'
@@ -120,8 +120,8 @@ gem 'acts-as-taggable-on', '~> 3.4'
 
 # Background jobs
 gem 'sinatra', '~> 1.4.4', require: nil
-gem 'sidekiq', '3.3.0'
-gem 'sidetiq', '~> 0.6.3'
+gem 'sidekiq', '~> 3.5.0'
+gem 'sidekiq-cron', '~> 0.3.0'
 
 # HTTP requests
 gem "httparty", '~> 0.13.3'
@@ -171,6 +171,7 @@ gem "underscore-rails", "~> 1.4.4"
 
 # Sanitize user input
 gem "sanitize", '~> 2.0'
+gem 'babosa', '~> 1.0.2'
 
 # Protect against bruteforcing
 gem "rack-attack", '~> 4.3.0'
@@ -204,6 +205,7 @@ gem 'raphael-rails',      '~> 2.1.2'
 gem 'request_store',      '~> 1.2.0'
 gem 'select2-rails',      '~> 3.5.9'
 gem 'virtus',             '~> 1.0.1'
+gem 'net-ssh',            '~> 3.0.1'
 
 group :development do
   gem "foreman"
diff --git a/Gemfile.lock b/Gemfile.lock
index cd1855758b2083d135a4f7932efe20443a42e70f..5d70788d9817b561a5e5b83ce6fb23bd62de88b9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -73,6 +73,7 @@ GEM
       descendants_tracker (~> 0.0.4)
       ice_nine (~> 0.11.0)
       thread_safe (~> 0.3, >= 0.3.1)
+    babosa (1.0.2)
     bcrypt (3.1.10)
     benchmark-ips (2.3.0)
     better_errors (1.0.1)
@@ -116,8 +117,23 @@ GEM
       activemodel (>= 3.2.0)
       activesupport (>= 3.2.0)
       json (>= 1.7)
-    celluloid (0.16.0)
-      timers (~> 4.0.0)
+    celluloid (0.17.2)
+      celluloid-essentials
+      celluloid-extras
+      celluloid-fsm
+      celluloid-pool
+      celluloid-supervision
+      timers (>= 4.1.1)
+    celluloid-essentials (0.20.5)
+      timers (>= 4.1.1)
+    celluloid-extras (0.20.5)
+      timers (>= 4.1.1)
+    celluloid-fsm (0.20.5)
+      timers (>= 4.1.1)
+    celluloid-pool (0.20.5)
+      timers (>= 4.1.1)
+    celluloid-supervision (0.20.5)
+      timers (>= 4.1.1)
     charlock_holmes (0.7.3)
     chunky_png (1.3.5)
     cliver (0.3.2)
@@ -369,7 +385,6 @@ GEM
       multi_xml (>= 0.5.2)
     httpclient (2.7.0.1)
     i18n (0.7.0)
-    ice_cube (0.11.1)
     ice_nine (0.11.1)
     inflecto (0.0.2)
     ipaddress (0.8.0)
@@ -640,6 +655,7 @@ GEM
       sexp_processor (~> 4.1)
     rubyntlm (0.5.2)
     rubypants (0.2.0)
+    rufus-scheduler (3.1.10)
     rugged (0.23.3)
     safe_yaml (1.0.4)
     sanitize (2.1.0)
@@ -667,16 +683,15 @@ GEM
       rack
     shoulda-matchers (2.8.0)
       activesupport (>= 3.0.0)
-    sidekiq (3.3.0)
-      celluloid (>= 0.16.0)
-      connection_pool (>= 2.0.0)
-      json
-      redis (>= 3.0.6)
-      redis-namespace (>= 1.3.1)
-    sidetiq (0.6.3)
-      celluloid (>= 0.14.1)
-      ice_cube (= 0.11.1)
-      sidekiq (>= 3.0.0)
+    sidekiq (3.5.3)
+      celluloid (~> 0.17.2)
+      connection_pool (~> 2.2, >= 2.2.0)
+      json (~> 1.0)
+      redis (~> 3.2, >= 3.2.1)
+      redis-namespace (~> 1.5, >= 1.5.2)
+    sidekiq-cron (0.3.1)
+      rufus-scheduler (>= 2.0.24)
+      sidekiq (>= 2.17.3)
     simple_oauth (0.1.9)
     simplecov (0.10.0)
       docile (~> 1.1.0)
@@ -742,7 +757,7 @@ GEM
     thor (0.19.1)
     thread_safe (0.3.5)
     tilt (1.4.1)
-    timers (4.0.4)
+    timers (4.1.1)
       hitimes
     timfel-krb5-auth (0.8.3)
     tinder (1.10.1)
@@ -823,6 +838,7 @@ DEPENDENCIES
   asciidoctor (~> 1.5.2)
   attr_encrypted (~> 1.3.4)
   awesome_print (~> 1.2.0)
+  babosa (~> 1.0.2)
   benchmark-ips
   better_errors (~> 1.0.1)
   binding_of_caller (~> 0.7.2)
@@ -924,6 +940,7 @@ DEPENDENCIES
   request_store (~> 1.2.0)
   rerun (~> 0.10.0)
   responders (~> 2.0)
+  rouge (~> 1.10.1)
   rqrcode-rails3 (~> 0.1.7)
   rspec-rails (~> 3.3.0)
   rubocop (~> 0.28.0)
@@ -936,8 +953,8 @@ DEPENDENCIES
   settingslogic (~> 2.0.9)
   sham_rack
   shoulda-matchers (~> 2.8.0)
-  sidekiq (= 3.3.0)
-  sidetiq (~> 0.6.3)
+  sidekiq (~> 3.5.0)
+  sidekiq-cron (~> 0.3.0)
   simplecov (~> 0.10.0)
   sinatra (~> 1.4.4)
   six (~> 0.2.0)
diff --git a/README.md b/README.md
index c59c8593eba16e7c818b5a6b56c316a224a7e286..c459e67baa198a6bce6bd69e3078723d03115eb9 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,7 @@ There are a lot of [third-party applications integrating with GitLab](https://ab
 
 ## GitLab release cycle
 
-Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases are published when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
+For more information about the release process see the [release documentation](http://doc.gitlab.com/ce/release/).
 
 ## Upgrading
 
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 09b48fe5572ab09b4bc97430d3d6a6f5ce846866..96fd8f8773ee43e4c4f67fea63d07c5f677170e8 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -88,4 +88,9 @@ class @AwardsHandler
         callback.call()
 
   findEmojiIcon: (emoji) ->
-    $(".icon[data-emoji='" + emoji + "']")
\ No newline at end of file
+    $(".icon[data-emoji='" + emoji + "']")
+
+  scrollToAwards: ->
+    $('body, html').animate({
+      scrollTop: $('.awards').offset().top - 80
+    }, 200)
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
index b39ab0c447580cbd7f5ef80f38f395da8f1bba1b..9b59d4e57f7ccf8e22ea9257b736c7b432512bdc 100644
--- a/app/assets/javascripts/flash.js.coffee
+++ b/app/assets/javascripts/flash.js.coffee
@@ -1,12 +1,16 @@
 class @Flash
   constructor: (message, type)->
-    flash = $(".flash-container")
-    flash.html("")
+    @flash = $(".flash-container")
+    @flash.html("")
 
-    $('<div/>',
+    innerDiv = $('<div/>',
       class: "flash-#{type}",
       text: message
-    ).appendTo(".flash-container")
+    )
+    innerDiv.appendTo(".flash-container")
 
-    flash.click -> $(@).fadeOut()
-    flash.show()
+    @flash.click -> $(@).fadeOut()
+    @flash.show()
+
+  pin: ->
+    @flash.addClass('flash-pinned flash-raised')
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 7de7632201ddc10da38369321aac4ef08434e267..533d00bfb0c522a969819f53a25174546aca5e8e 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -111,6 +111,12 @@ class @Notes
   Note: for rendering inline notes use renderDiscussionNote
   ###
   renderNote: (note) ->
+    unless note.valid
+      if note.award
+        flash = new Flash('You have already used this award emoji!', 'alert')
+        flash.pin()
+      return
+
     # render note if it not present in loaded list
     # or skip if rendered
     if @isNewNote(note) && !note.award
@@ -122,6 +128,7 @@ class @Notes
 
     if note.award
       awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
+      awards_handler.scrollToAwards()
 
   ###
   Check if note does not exists on page
@@ -362,8 +369,8 @@ class @Notes
     note = $(this).closest(".note")
     note.find(".note-attachment").remove()
     note.find(".note-body > .note-text").show()
-    note.find(".js-note-attachment-delete").hide()
-    note.find(".note-edit-form").hide()
+    note.find(".note-header").show()
+    note.find(".current-note-edit-form").remove()
 
   ###
   Called when clicking on the "reply" button for a diff line.
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 82eb50ad4be068473bac0507f6fc1a1aa75750b0..1b723021d76d2d8ea998eba507a48e43e1a676a8 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -15,3 +15,13 @@
     @extend .alert-danger;
   }
 }
+
+.flash-pinned {
+  position: fixed;
+  top: 80px;
+  width: 80%;
+}
+
+.flash-raised {
+  z-index: 1000;
+}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index cc660529cb4d00e6a655bf76cd17f55b505b76e7..2b044786738566bae2ef7ab6a04693d11393ec95 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -73,11 +73,8 @@
 }
 
 .referenced-users {
-  padding: 10px 0;
-  color: #999;
-  margin-left: 10px;
-  margin-top: 1px;
-  margin-right: 130px;
+  color: #4c4e54;
+  padding-top: 10px;
 }
 
 .md-preview-holder {
diff --git a/app/controllers/concerns/global_milestones.rb b/app/controllers/concerns/global_milestones.rb
index b428249acd3eed58de5dbdad1fb8a033ac689d81..3e4c0e63601d4296333a7327c7973ed4af5b661a 100644
--- a/app/controllers/concerns/global_milestones.rb
+++ b/app/controllers/concerns/global_milestones.rb
@@ -2,8 +2,10 @@ module GlobalMilestones
   extend ActiveSupport::Concern
 
   def milestones
+    epoch = DateTime.parse('1970-01-01')
     @milestones = MilestonesFinder.new.execute(@projects, params)
     @milestones = GlobalMilestone.build_collection(@milestones)
+    @milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
     @milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
   end
 
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 10233222ee1b8f76c6a78fe1a29c05cfbf33122a..0c2a350bc39ef24b1ab809d4ee5d75d4d6c4cfc1 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -46,7 +46,7 @@ class Groups::MilestonesController < Groups::ApplicationController
   end
 
   def milestone_path(title)
-    group_milestone_path(@group, title.parameterize, title: title)
+    group_milestone_path(@group, title.to_slug.to_s, title: title)
   end
 
   def projects
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index c7569541899602a7695188b326fd57df9256e102..6a62880cb71248871eb4e1214cc21b31db786ca6 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -25,13 +25,12 @@ class Projects::HooksController < Projects::ApplicationController
 
   def test
     if !@project.empty_repo?
-      status = TestHookService.new.execute(hook, current_user)
+      status, message = TestHookService.new.execute(hook, current_user)
 
       if status
         flash[:notice] = 'Hook successfully executed.'
       else
-        flash[:alert] = 'Hook execution failed. '\
-                        'Ensure hook URL is correct and service is up.'
+        flash[:alert] = "Hook execution failed: #{message}"
       end
     else
       flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 5ac18446aa7dccb01bf63685da25ee1d7f3b8cd3..88b949a27ab931bc823a5e2856a5630ea958136c 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -131,16 +131,25 @@ class Projects::NotesController < Projects::ApplicationController
   end
 
   def render_note_json(note)
-    render json: {
-      id: note.id,
-      discussion_id: note.discussion_id,
-      html: note_to_html(note),
-      award: note.is_award,
-      emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
-      note: note.note,
-      discussion_html: note_to_discussion_html(note),
-      discussion_with_diff_html: note_to_discussion_with_diff_html(note)
-    }
+    if note.valid?
+      render json: {
+        valid: true,
+        id: note.id,
+        discussion_id: note.discussion_id,
+        html: note_to_html(note),
+        award: note.is_award,
+        emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
+        note: note.note,
+        discussion_html: note_to_discussion_html(note),
+        discussion_with_diff_html: note_to_discussion_with_diff_html(note)
+      }
+    else
+      render json: {
+        valid: false,
+        award: note.is_award,
+        errors: note.errors
+      }
+    end
   end
 
   def authorize_admin_note!
diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb
index b704e878903b5f460d0d8b3a33955e3f2d051829..630c73c2a94bd79f63e89a861614b9fe3a26ad3b 100644
--- a/app/finders/milestones_finder.rb
+++ b/app/finders/milestones_finder.rb
@@ -1,7 +1,7 @@
 class MilestonesFinder
   def execute(projects, params)
     milestones = Milestone.of_projects(projects)
-    milestones = milestones.order("due_date ASC")
+    milestones = milestones.reorder("due_date ASC")
 
     case params[:state]
     when 'closed' then milestones.closed
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 3230ff1b0048d6d51f325c8a77e3d011c0f2f79b..21f962df206d5814d5bf4af0551e0c4ba6412119 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -209,7 +209,7 @@ module ApplicationHelper
       title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
       data: { toggle: 'tooltip', placement: placement, container: 'body' }
 
-    element += javascript_tag "$('.js-timeago').timeago()" unless skip_js
+    element += javascript_tag "$('.js-timeago').last().timeago()" unless skip_js
 
     element
   end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index ad43892b6399cc14e1312afd52f34cbefa1654e9..a42cbcff1829b12b5215eb595e0b3af2d1c59dbc 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -28,7 +28,9 @@ module MilestonesHelper
         Milestone.where(project_id: @projects)
       end.active
 
+    epoch = DateTime.parse('1970-01-01')
     grouped_milestones = GlobalMilestone.build_collection(milestones)
+    grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
     grouped_milestones.unshift(Milestone::None)
     grouped_milestones.unshift(Milestone::Any)
 
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 5ddcf3d9a0b19895859bc249bcc9756673be7d19..1880ad9f33c34d8d340e3e79f0f4f80b540417d6 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -43,12 +43,12 @@ class ApplicationSetting < ActiveRecord::Base
 
   validates :home_page_url,
     allow_blank: true,
-    format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" },
+    url: true,
     if: :home_page_url_column_exist
 
   validates :after_sign_out_path,
     allow_blank: true,
-    format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
+    url: true
 
   validates :admin_notification_email,
     allow_blank: true,
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 05f5e9796959d577298a87dd0bc366f24d686865..ad514706160bd49b15f20d3226943b738ee11cee 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -16,12 +16,12 @@
 class BroadcastMessage < ActiveRecord::Base
   include Sortable
 
-  validates :message, presence: true
+  validates :message,   presence: true
   validates :starts_at, presence: true
-  validates :ends_at, presence: true
+  validates :ends_at,   presence: true
 
-  validates :color, format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true
-  validates :font,  format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true
+  validates :color, allow_blank: true, color: true
+  validates :font,  allow_blank: true, color: true
 
   def self.current
     where("ends_at > :now AND starts_at < :now", now: Time.zone.now).last
diff --git a/app/models/ci/web_hook.rb b/app/models/ci/web_hook.rb
index 7ca16a1bde82fc7f2d603fe010d48ba6d844d4c5..0dc15eb6683c9de098246428341088482b768d41 100644
--- a/app/models/ci/web_hook.rb
+++ b/app/models/ci/web_hook.rb
@@ -20,8 +20,7 @@ module Ci
     # HTTParty timeout
     default_timeout 10
 
-    validates :url, presence: true,
-                    format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
+    validates :url, presence: true, url: true
 
     def execute(data)
       parsed_url = URI.parse(url)
diff --git a/app/models/commit.rb b/app/models/commit.rb
index c0998a45709191d5c9b34086dca2699f9bc011c8..8ae5325d16a66d93ee952d3964224b8951725fb6 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -147,10 +147,10 @@ class Commit
     description.present?
   end
 
-  def hook_attrs
+  def hook_attrs(with_changed_files: false)
     path_with_namespace = project.path_with_namespace
 
-    {
+    data = {
       id: id,
       message: safe_message,
       timestamp: committed_date.xmlschema,
@@ -160,6 +160,12 @@ class Commit
         email: author_email
       }
     }
+
+    if with_changed_files
+      data.merge!(repo_changes)
+    end
+
+    data
   end
 
   # Discover issues should be closed when this commit is pushed to a project's
@@ -208,4 +214,22 @@ class Commit
   def status
     ci_commit.try(:status) || :not_found
   end
+
+  private
+
+  def repo_changes
+    changes = { added: [], modified: [], removed: [] }
+
+    diffs.each do |diff|
+      if diff.deleted_file
+        changes[:removed] << diff.old_path
+      elsif diff.renamed_file || diff.new_file
+        changes[:added] << diff.new_path
+      else
+        changes[:modified] << diff.new_path
+      end
+    end
+
+    changes
+  end
 end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 1321ccd963fb449491967fe1365c32394899e56a..8bfc79d88f8d1ad82027123a7f9413ba5ecb3df9 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -16,7 +16,15 @@ class GlobalMilestone
   end
 
   def safe_title
-    @title.parameterize
+    @title.to_slug.to_s
+  end
+
+  def expired?
+    if due_date
+      due_date.past?
+    else
+      false
+    end
   end
 
   def projects
@@ -98,4 +106,25 @@ class GlobalMilestone
   def complete?
     total_items_count == closed_items_count
   end
+
+  def due_date
+    return @due_date if defined?(@due_date)
+
+    @due_date =
+      if @milestones.all? { |x| x.due_date == @milestones.first.due_date }
+        @milestones.first.due_date
+      else
+        nil
+      end
+  end
+
+  def expires_at
+    if due_date
+      if due_date.past?
+        "expired at #{due_date.stamp("Aug 21, 2011")}"
+      else
+        "expires at #{due_date.stamp("Aug 21, 2011")}"
+      end
+    end
+  end
 end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index d6c6f415c4a1fd23e27686222589dbd70c046cb6..715ec5908b7f8d42221422ba39254b9bc822ed61 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -31,37 +31,38 @@ class WebHook < ActiveRecord::Base
   # HTTParty timeout
   default_timeout Gitlab.config.gitlab.webhook_timeout
 
-  validates :url, presence: true,
-                  format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
+  validates :url, presence: true, url: true
 
   def execute(data, hook_name)
     parsed_url = URI.parse(url)
     if parsed_url.userinfo.blank?
-      WebHook.post(url,
-                   body: data.to_json,
-                   headers: {
-                     "Content-Type" => "application/json",
-                     "X-Gitlab-Event" => hook_name.singularize.titleize
-                   },
-                   verify: enable_ssl_verification)
+      response = WebHook.post(url,
+                              body: data.to_json,
+                              headers: {
+                                  "Content-Type" => "application/json",
+                                  "X-Gitlab-Event" => hook_name.singularize.titleize
+                              },
+                              verify: enable_ssl_verification)
     else
       post_url = url.gsub("#{parsed_url.userinfo}@", "")
       auth = {
         username: URI.decode(parsed_url.user),
         password: URI.decode(parsed_url.password),
       }
-      WebHook.post(post_url,
-                   body: data.to_json,
-                   headers: {
-                     "Content-Type" => "application/json",
-                     "X-Gitlab-Event" => hook_name.singularize.titleize
-                   },
-                   verify: enable_ssl_verification,
-                   basic_auth: auth)
+      response = WebHook.post(post_url,
+                              body: data.to_json,
+                              headers: {
+                                  "Content-Type" => "application/json",
+                                  "X-Gitlab-Event" => hook_name.singularize.titleize
+                              },
+                              verify: enable_ssl_verification,
+                              basic_auth: auth)
     end
-  rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
+
+    [response.code == 200, ActionView::Base.full_sanitizer.sanitize(response.to_s)]
+  rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
     logger.error("WebHook Error => #{e}")
-    false
+    [false, e.to_s]
   end
 
   def async_execute(data, hook_name)
diff --git a/app/models/label.rb b/app/models/label.rb
index bef6063fe88f3e9f8744c9dd91871dfeb15cf4fb..220da10a6abe86fabe7c9ec4b670f2e4a7166372 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -27,9 +27,7 @@ class Label < ActiveRecord::Base
   has_many :label_links, dependent: :destroy
   has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
 
-  validates :color,
-            format: { with: /\A#[0-9A-Fa-f]{6}\Z/ },
-            allow_blank: false
+  validates :color, color: true, allow_blank: false
   validates :project, presence: true, unless: Proc.new { |service| service.template? }
 
   # Don't allow '?', '&', and ',' for label titles
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 4bcbd86a5435616680b15a8f31b8bee034e56d76..60fd2b9a757d422f1772ed71d31699269cbd6470 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -312,7 +312,7 @@ class MergeRequest < ActiveRecord::Base
       work_in_progress: work_in_progress?
     }
 
-    unless last_commit.nil?
+    if last_commit
       attrs.merge!(last_commit: last_commit.hook_attrs)
     end
 
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 20b92e68d6100176484870b310972546b7af954a..1c4e101cc105708c9a324dfee7b67f4f8bdf40fb 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -23,19 +23,17 @@ class Namespace < ActiveRecord::Base
 
   validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
   validates :name,
-    presence: true, uniqueness: true,
     length: { within: 0..255 },
-    format: { with: Gitlab::Regex.namespace_name_regex,
-              message: Gitlab::Regex.namespace_name_regex_message }
+    namespace_name: true,
+    presence: true,
+    uniqueness: true
 
   validates :description, length: { within: 0..255 }
   validates :path,
-    uniqueness: { case_sensitive: false },
-    presence: true,
     length: { within: 1..255 },
-    exclusion: { in: Gitlab::Blacklist.path },
-    format: { with: Gitlab::Regex.namespace_regex,
-              message: Gitlab::Regex.namespace_regex_message }
+    namespace: true,
+    presence: true,
+    uniqueness: { case_sensitive: false }
 
   delegate :name, to: :owner, allow_nil: true, prefix: true
 
diff --git a/app/models/note.rb b/app/models/note.rb
index 1c6345e735c4b94154aecce51de775996354ce77..8d433c57ceb5c65bc6f3e5679e7c7fc879d56e60 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -39,9 +39,12 @@ class Note < ActiveRecord::Base
   delegate :name, to: :project, prefix: true
   delegate :name, :email, to: :author, prefix: true
 
+  before_validation :set_award!
+
   validates :note, :project, presence: true
   validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
-  validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
+  validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
+  validates :line_code, line_code: true, allow_blank: true
   # Attachments are deprecated and are handled by Markdown uploader
   validates :attachment, file_size: { maximum: :max_attachment_size }
 
@@ -348,4 +351,31 @@ class Note < ActiveRecord::Base
   def editable?
     !system?
   end
+
+  # Checks if note is an award added as a comment
+  #
+  # If note is an award, this method sets is_award to true
+  #   and changes content of the note to award name.
+  #
+  # Method is executed as a before_validation callback.
+  #
+  def set_award!
+    return unless awards_supported? && contains_emoji_only?
+    self.is_award = true
+    self.note = award_emoji_name
+  end
+
+  private
+
+  def awards_supported?
+    noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)
+  end
+
+  def contains_emoji_only?
+    note =~ /\A#{Gitlab::Markdown::EmojiFilter.emoji_pattern}\s?\Z/
+  end
+
+  def award_emoji_name
+    note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1]
+  end
 end
diff --git a/app/models/project.rb b/app/models/project.rb
index 6010770a5f2b8565d3761d514522ceb3300787ab..af034a6692b1bf70cacdfdc21fb7245ed25c0e05 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -152,7 +152,7 @@ class Project < ActiveRecord::Base
   validates_uniqueness_of :name, scope: :namespace_id
   validates_uniqueness_of :path, scope: :namespace_id
   validates :import_url,
-    format: { with: /\A#{URI.regexp(%w(ssh git http https))}\z/, message: 'should be a valid url' },
+    url: { protocols: %w(ssh git http https) },
     if: :external_import?
   validates :star_count, numericality: { greater_than_or_equal_to: 0 }
   validate :check_limit, on: :create
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index d31b12f539e9bb9dee0e3903761d418f7d2004e4..0a61ad96a0e9b6aedbfe13ccc92e68dc4642963b 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -23,10 +23,7 @@ class BambooService < CiService
 
   prop_accessor :bamboo_url, :build_key, :username, :password
 
-  validates :bamboo_url,
-    presence: true,
-    format: { with: /\A#{URI.regexp}\z/ },
-    if: :activated?
+  validates :bamboo_url, presence: true, url: true, if: :activated?
   validates :build_key, presence: true, if: :activated?
   validates :username,
     presence: true,
@@ -84,7 +81,7 @@ class BambooService < CiService
   def supported_events
     %w(push)
   end
-  
+
   def build_info(sha)
     url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
 
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index 06c3922593c6b996c4a5f9db49efbbe309c67107..08e5ccb38555f2b6381c1e0244c1f441d1a66e87 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -19,14 +19,11 @@
 #
 
 class DroneCiService < CiService
-  
+
   prop_accessor :drone_url, :token, :enable_ssl_verification
-  validates :drone_url,
-    presence: true,
-    format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
-  validates :token,
-    presence: true,
-    if: :activated?
+
+  validates :drone_url, presence: true, url: true, if: :activated?
+  validates :token, presence: true, if: :activated?
 
   after_save :compose_service_hook, if: :activated?
 
@@ -58,16 +55,16 @@ class DroneCiService < CiService
   end
 
   def merge_request_status_path(iid, sha = nil, ref = nil)
-    url = [drone_url, 
-           "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}", 
+    url = [drone_url,
+           "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
            "?access_token=#{token}"]
 
     URI.join(*url).to_s
   end
 
   def commit_status_path(sha, ref)
-    url = [drone_url, 
-           "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}", 
+    url = [drone_url,
+           "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
            "?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"]
 
     URI.join(*url).to_s
@@ -114,15 +111,15 @@ class DroneCiService < CiService
   end
 
   def merge_request_page(iid, sha, ref)
-    url = [drone_url, 
+    url = [drone_url,
            "gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"]
 
     URI.join(*url).to_s
   end
 
   def commit_page(sha, ref)
-    url = [drone_url, 
-           "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}", 
+    url = [drone_url,
+           "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
            "?branch=#{URI::encode(ref.to_s)}"]
 
     URI.join(*url).to_s
@@ -163,10 +160,10 @@ class DroneCiService < CiService
   end
 
   def push_valid?(data)
-    opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id, 
+    opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
                                                                 source_branch: Gitlab::Git.ref_name(data[:ref]))
 
-    opened_merge_requests.empty? && data[:total_commits_count] > 0 && 
+    opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
       !Gitlab::Git.blank_ref?(data[:after])
   end
 
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index 9c46af7e7213f08f30e47c1668ae74e99cff6824..74c57949b4d8f1e2e67aaa489a68111beaf4371d 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -22,10 +22,8 @@ class ExternalWikiService < Service
   include HTTParty
 
   prop_accessor :external_wiki_url
-  validates :external_wiki_url,
-            presence: true,
-            format: { with: /\A#{URI.regexp}\z/ },
-            if: :activated?
+
+  validates :external_wiki_url, presence: true, url: true, if: :activated?
 
   def title
     'External Wiki'
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 0b0224612505e18f81386b5442103193865f5d29..29d4236745a15aa46be95a2eddbd695cbf55c6cc 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -23,16 +23,16 @@ class TeamcityService < CiService
 
   prop_accessor :teamcity_url, :build_type, :username, :password
 
-  validates :teamcity_url,
-    presence: true,
-    format: { with: /\A#{URI.regexp}\z/ }, if: :activated?
+  validates :teamcity_url, presence: true, url: true, if: :activated?
   validates :build_type, presence: true, if: :activated?
   validates :username,
     presence: true,
-    if: ->(service) { service.password? }, if: :activated?
+    if: ->(service) { service.password? },
+    if: :activated?
   validates :password,
     presence: true,
-    if: ->(service) { service.username? }, if: :activated?
+    if: ->(service) { service.username? },
+    if: :activated?
 
   attr_accessor :response
 
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d247b0f50124b98405277b626dee258f6bc24898..1d43307e1e7f72a14a7c523beeb688e336846980 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1,7 +1,6 @@
 require 'securerandom'
 
 class Repository
-  class PreReceiveError < StandardError; end
   class CommitError < StandardError; end
 
   include Gitlab::ShellAdapter
@@ -101,17 +100,26 @@ class Repository
   end
 
   def find_branch(name)
-    branches.find { |branch| branch.name == name }
+    raw_repository.branches.find { |branch| branch.name == name }
   end
 
   def find_tag(name)
-    tags.find { |tag| tag.name == name }
+    raw_repository.tags.find { |tag| tag.name == name }
   end
 
-  def add_branch(branch_name, ref)
-    expire_branches_cache
+  def add_branch(user, branch_name, target)
+    oldrev = Gitlab::Git::BLANK_SHA
+    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+    target = commit(target).try(:id)
+
+    return false unless target
+
+    GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
+      rugged.branches.create(branch_name, target)
+    end
 
-    gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
+    expire_branches_cache
+    find_branch(branch_name)
   end
 
   def add_tag(tag_name, ref, message = nil)
@@ -120,10 +128,20 @@ class Repository
     gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
   end
 
-  def rm_branch(branch_name)
+  def rm_branch(user, branch_name)
     expire_branches_cache
 
-    gitlab_shell.rm_branch(path_with_namespace, branch_name)
+    branch = find_branch(branch_name)
+    oldrev = branch.try(:target)
+    newrev = Gitlab::Git::BLANK_SHA
+    ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+
+    GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
+      rugged.branches.delete(branch_name)
+    end
+
+    expire_branches_cache
+    true
   end
 
   def rm_tag(tag_name)
@@ -550,7 +568,6 @@ class Repository
   def commit_with_hooks(current_user, branch)
     oldrev = Gitlab::Git::BLANK_SHA
     ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
-    gl_id = Gitlab::ShellEnv.gl_id(current_user)
     was_empty = empty?
 
     # Create temporary ref
@@ -569,15 +586,7 @@ class Repository
       raise CommitError.new('Failed to create commit')
     end
 
-    # Run GitLab pre-receive hook
-    pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo)
-    pre_receive_hook_status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref)
-
-    # Run GitLab update hook
-    update_hook = Gitlab::Git::Hook.new('update', path_to_repo)
-    update_hook_status = update_hook.trigger(gl_id, oldrev, newrev, ref)
-
-    if pre_receive_hook_status && update_hook_status
+    GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
       if was_empty
         # Create branch
         rugged.references.create(ref, newrev)
@@ -592,16 +601,11 @@ class Repository
           raise CommitError.new('Commit was rejected because branch received new push')
         end
       end
-
-      # Run GitLab post receive hook
-      post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo)
-      post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
-    else
-      # Remove tmp ref and return error to user
-      rugged.references.delete(tmp_ref)
-
-      raise PreReceiveError.new('Commit was rejected by git hook')
     end
+  rescue GitHooksService::PreReceiveError
+    # Remove tmp ref and return error to user
+    rugged.references.delete(tmp_ref)
+    raise
   end
 
   private
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index d8fe65b06f6d34238f0316e2bfa7c323394ad2f2..f36eda1531b803cabd4026f54d003d2d3f2710f7 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -21,7 +21,7 @@ class SentNotification < ActiveRecord::Base
   validates :reply_key, uniqueness: true
   validates :noteable_id, presence: true, unless: :for_commit?
   validates :commit_id, presence: true, if: :for_commit?
-  validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
+  validates :line_code, line_code: true, allow_blank: true
 
   class << self
     def reply_key
diff --git a/app/models/user.rb b/app/models/user.rb
index 719b49b16fe1e82fb61858cd24458586371f306d..cfed797e72507e6849b1dfe9a7f45bedadcd2834 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -148,11 +148,9 @@ class User < ActiveRecord::Base
   validates :bio, length: { maximum: 255 }, allow_blank: true
   validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
   validates :username,
+    namespace: true,
     presence: true,
-    uniqueness: { case_sensitive: false },
-    exclusion: { in: Gitlab::Blacklist.path },
-    format: { with: Gitlab::Regex.namespace_regex,
-              message: Gitlab::Regex.namespace_regex_message }
+    uniqueness: { case_sensitive: false }
 
   validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
   validate :namespace_uniq, if: ->(user) { user.username_changed? }
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index cf7ae4345f36b85fa8d21425e7b991ce4e5a8ca4..de18f3bc5567f6c2194340fc3ed26dd76c3fe9e8 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -13,8 +13,7 @@ class CreateBranchService < BaseService
       return error('Branch already exists')
     end
 
-    repository.add_branch(branch_name, ref)
-    new_branch = repository.find_branch(branch_name)
+    new_branch = repository.add_branch(current_user, branch_name, ref)
 
     if new_branch
       push_data = build_push_data(project, current_user, new_branch)
@@ -27,6 +26,8 @@ class CreateBranchService < BaseService
     else
       error('Invalid reference name')
     end
+  rescue GitHooksService::PreReceiveError
+    error('Branch creation was rejected by Git hook')
   end
 
   def success(branch)
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index b19b112a0c4052560c450854dccaebc9e471c531..22bf9dd935e7122d77717c1372e86cba7e68f632 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -24,7 +24,7 @@ class DeleteBranchService < BaseService
       return error('You dont have push access to repo', 405)
     end
 
-    if repository.rm_branch(branch_name)
+    if repository.rm_branch(current_user, branch_name)
       push_data = build_push_data(branch)
 
       EventCreateService.new.push(project, current_user, push_data)
@@ -35,6 +35,8 @@ class DeleteBranchService < BaseService
     else
       error('Failed to remove branch')
     end
+  rescue GitHooksService::PreReceiveError
+    error('Branch deletion was rejected by Git hook')
   end
 
   def error(message, return_code = 400)
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 008833eed80aa9705256926ac6f07e9c8c0f08df..f50aaf2eb52ae38f04f03b0f65ae15b3fc928516 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -26,7 +26,7 @@ module Files
       else
         error("Something went wrong. Your changes were not committed")
       end
-    rescue Repository::CommitError, Repository::PreReceiveError, ValidationError => ex
+    rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex
       error(ex.message)
     end
 
diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8f5c3393dfc8e2b56fcb78817e8c15a98a4e2e63
--- /dev/null
+++ b/app/services/git_hooks_service.rb
@@ -0,0 +1,28 @@
+class GitHooksService
+  PreReceiveError = Class.new(StandardError)
+
+  def execute(user, repo_path, oldrev, newrev, ref)
+    @repo_path  = repo_path
+    @user       = Gitlab::ShellEnv.gl_id(user)
+    @oldrev     = oldrev
+    @newrev     = newrev
+    @ref        = ref
+
+    %w(pre-receive update).each do |hook_name|
+      unless run_hook(hook_name)
+        raise PreReceiveError.new("Git operation was rejected by #{hook_name} hook")
+      end
+    end
+
+    yield
+
+    run_hook('post-receive')
+  end
+
+  private
+
+  def run_hook(name)
+    hook = Gitlab::Git::Hook.new(name, @repo_path)
+    hook.trigger(@user, @oldrev, @newrev, @ref)
+  end
+end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index dbff58dfb9c78564d7e20ddb5613c4eafe93effd..a8486e6a5a1ecb3c62f7d507e666d9eee9e2943a 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,11 +5,6 @@ module Notes
       note.author = current_user
       note.system = false
 
-      if contains_emoji_only?(params[:note])
-        note.is_award = true
-        note.note = emoji_name(params[:note])
-      end
-
       if note.save
         notification_service.new_note(note)
 
@@ -33,13 +28,5 @@ module Notes
       note.project.execute_hooks(note_data, :note_hooks)
       note.project.execute_services(note_data, :note_hooks)
     end
-
-    def contains_emoji_only?(note)
-      note =~ /\A:[-_+[:alnum:]]*:\s?\z/
-    end
-
-    def emoji_name(note)
-      note.match(/\A:([-_+[:alnum:]]*):\s?/)[1]
-    end
   end
 end
diff --git a/app/validators/color_validator.rb b/app/validators/color_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..571d0007aa2d111bf6bd4416584e5493f19d78f5
--- /dev/null
+++ b/app/validators/color_validator.rb
@@ -0,0 +1,20 @@
+# ColorValidator
+#
+# Custom validator for web color codes. It requires the leading hash symbol and
+# will accept RGB triplet or hexadecimal formats.
+#
+# Example:
+#
+#   class User < ActiveRecord::Base
+#     validates :background_color, allow_blank: true, color: true
+#   end
+#
+class ColorValidator < ActiveModel::EachValidator
+  PATTERN = /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/.freeze
+
+  def validate_each(record, attribute, value)
+    unless value =~ PATTERN
+      record.errors.add(attribute, "must be a valid color code")
+    end
+  end
+end
diff --git a/lib/email_validator.rb b/app/validators/email_validator.rb
similarity index 63%
rename from lib/email_validator.rb
rename to app/validators/email_validator.rb
index f509f0a584336fc0b3a1a5cccd36477eeb4c7fd4..b35af10080399cd8358170ca2d97c9514196d4d2 100644
--- a/lib/email_validator.rb
+++ b/app/validators/email_validator.rb
@@ -1,3 +1,5 @@
+# EmailValidator
+#
 # Based on https://github.com/balexand/email_validator
 #
 # Extended to use only strict mode with following allowed characters:
@@ -6,15 +8,10 @@
 # See http://www.remote.org/jochen/mail/info/chars.html
 #
 class EmailValidator < ActiveModel::EachValidator
-  @@default_options = {}
-
-  def self.default_options
-    @@default_options
-  end
+  PATTERN = /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i.freeze
 
   def validate_each(record, attribute, value)
-    options = @@default_options.merge(self.options)
-    unless value =~ /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i
+    unless value =~ PATTERN
       record.errors.add(attribute, options[:message] || :invalid)
     end
   end
diff --git a/app/validators/line_code_validator.rb b/app/validators/line_code_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ed29e5aeb67f08f3e401db4c0f5f815ecf4766b9
--- /dev/null
+++ b/app/validators/line_code_validator.rb
@@ -0,0 +1,12 @@
+# LineCodeValidator
+#
+# Custom validator for GitLab line codes.
+class LineCodeValidator < ActiveModel::EachValidator
+  PATTERN = /\A[a-z0-9]+_\d+_\d+\z/.freeze
+
+  def validate_each(record, attribute, value)
+    unless value =~ PATTERN
+      record.errors.add(attribute, "must be a valid line code")
+    end
+  end
+end
diff --git a/app/validators/namespace_name_validator.rb b/app/validators/namespace_name_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2e51af2982d59035420e458e50e073f27153e645
--- /dev/null
+++ b/app/validators/namespace_name_validator.rb
@@ -0,0 +1,10 @@
+# NamespaceNameValidator
+#
+# Custom validator for GitLab namespace name strings.
+class NamespaceNameValidator < ActiveModel::EachValidator
+  def validate_each(record, attribute, value)
+    unless value =~ Gitlab::Regex.namespace_name_regex
+      record.errors.add(attribute, Gitlab::Regex.namespace_name_regex_message)
+    end
+  end
+end
diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..10e35ce665ae61233eba90b7d1c534fbcf5d6aef
--- /dev/null
+++ b/app/validators/namespace_validator.rb
@@ -0,0 +1,50 @@
+# NamespaceValidator
+#
+# Custom validator for GitLab namespace values.
+#
+# Values are checked for formatting and exclusion from a list of reserved path
+# names.
+class NamespaceValidator < ActiveModel::EachValidator
+  RESERVED = %w(
+    admin
+    all
+    assets
+    ci
+    dashboard
+    files
+    groups
+    help
+    hooks
+    issues
+    merge_requests
+    notes
+    profile
+    projects
+    public
+    repository
+    s
+    search
+    services
+    snippets
+    teams
+    u
+    unsubscribes
+    users
+  ).freeze
+
+  def validate_each(record, attribute, value)
+    unless value =~ Gitlab::Regex.namespace_regex
+      record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
+    end
+
+    if reserved?(value)
+      record.errors.add(attribute, "#{value} is a reserved name")
+    end
+  end
+
+  private
+
+  def reserved?(value)
+    RESERVED.include?(value)
+  end
+end
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2848b9cd33d1603fd5860315f99dbe961744ad21
--- /dev/null
+++ b/app/validators/url_validator.rb
@@ -0,0 +1,36 @@
+# UrlValidator
+#
+# Custom validator for URLs.
+#
+# By default, only URLs for the HTTP(S) protocols will be considered valid.
+# Provide a `:protocols` option to configure accepted protocols.
+#
+# Example:
+#
+#   class User < ActiveRecord::Base
+#     validates :personal_url, url: true
+#
+#     validates :ftp_url, url: { protocols: %w(ftp) }
+#
+#     validates :git_url, url: { protocols: %w(http https ssh git) }
+#   end
+#
+class UrlValidator < ActiveModel::EachValidator
+  def validate_each(record, attribute, value)
+    unless valid_url?(value)
+      record.errors.add(attribute, "must be a valid URL")
+    end
+  end
+
+  private
+
+  def default_options
+    @default_options ||= { protocols: %w(http https) }
+  end
+
+  def valid_url?(value)
+    options = default_options.merge(self.options)
+
+    value =~ /\A#{URI.regexp(options[:protocols])}\z/
+  end
+end
diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml
index 55080d6b3fe5f85e773af02a5e78feb271da1145..7c882a327025698db068b0f4e1149a9bd1543d50 100644
--- a/app/views/dashboard/milestones/_milestone.html.haml
+++ b/app/views/dashboard/milestones/_milestone.html.haml
@@ -16,7 +16,10 @@
       = milestone_progress_bar(milestone)
   .row
     .col-sm-6
-      - milestone.milestones.each do |milestone|
-        = link_to milestone_path(milestone) do
-          %span.label.label-gray
-            = milestone.project.name_with_namespace
+      .expiration
+        = render 'shared/milestone_expired', milestone: milestone
+      .projects
+        - milestone.milestones.each do |milestone|
+          = link_to milestone_path(milestone) do
+            %span.label.label-gray
+              = milestone.project.name_with_namespace
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 8218cf11201899929c9b2414c13b4e73b7bc2eb0..54c818baaf41168f2673bda0a3b9961c8d9f514e 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -8,17 +8,18 @@
         %a.js-md-preview-button(href="#md-preview-holder" tabindex="-1")
           Preview
 
-    - if defined?(referenced_users) && referenced_users
-      %span.referenced-users.pull-left.hide
+  %div
+    .md-write-holder
+      = yield
+    .md.md-preview-holder.hide
+      .js-md-preview{class: (preview_class if defined?(preview_class))}
+
+  - if defined?(referenced_users) && referenced_users
+    %div.referenced-users.hide
+      %span
         = icon('exclamation-triangle')
         You are about to add
         %strong
           %span.js-referenced-users-count 0
           people
         to the discussion. Proceed with caution.
-
-  %div
-    .md-write-holder
-      = yield
-    .md.md-preview-holder.hide
-      .js-md-preview{class: (preview_class if defined?(preview_class))}
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index b3392d00e0110274889d3f8cc5063bd3eab101f4..b77e9f9f4038b684c89a47df060137f9fc8ea0d8 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -3,9 +3,8 @@
     - if diff_file.diff.submodule?
       %span
         = icon('archive fw')
-        - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
         %strong
-          = submodule_link(submodule_item, @commit.id, project.repository)
+          = submodule_link(blob, @commit.id, project.repository)
     - else
       %span
         = blob_icon blob.mode, blob.name
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 334172b976f5da2958d95d0b2d6d74b3232a8edc..d6a44c9f0a1ce776e1267190b7c80da39976bf05 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -18,11 +18,7 @@
 
   .row
     .col-sm-6
-      - if milestone.expired? and not milestone.closed?
-        %span.cred (Expired)
-      - if milestone.expires_at
-        %span
-          = milestone.expires_at
+      = render 'shared/milestone_expired', milestone: milestone
     .col-sm-6
       - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
         = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do
diff --git a/app/views/shared/_milestone_expired.html.haml b/app/views/shared/_milestone_expired.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b8eef15fbec4e37ddea3ef780a261a21060f7347
--- /dev/null
+++ b/app/views/shared/_milestone_expired.html.haml
@@ -0,0 +1,5 @@
+- if milestone.expired? and not milestone.closed?
+  %span.cred (Expired)
+- if milestone.expires_at
+  %span
+    = milestone.expires_at
diff --git a/app/workers/stuck_ci_builds_worker.rb b/app/workers/stuck_ci_builds_worker.rb
index 4e5eddbaba1c68a8de1717eac0dab0f075544c56..ca594e77e7cd844693b42d7b7c21c15fc6ed1741 100644
--- a/app/workers/stuck_ci_builds_worker.rb
+++ b/app/workers/stuck_ci_builds_worker.rb
@@ -1,11 +1,8 @@
 class StuckCiBuildsWorker
   include Sidekiq::Worker
-  include Sidetiq::Schedulable
 
   BUILD_STUCK_TIMEOUT = 1.day
 
-  recurrence { daily }
-
   def perform
     Rails.logger.info 'Cleaning stuck builds'
 
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index e856499732e949334e401a6740846f1d706ba24b..6e5701e33dae579643fa4b305e111a8f3e9e6f5f 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -17,6 +17,12 @@ Sidekiq.configure_server do |config|
     chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
     chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
   end
+
+  # Sidekiq-cron: load recurring jobs from schedule.yml
+  schedule_file = 'config/schedule.yml'
+  if File.exists?(schedule_file)
+    Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
+  end
 end
 
 Sidekiq.configure_client do |config|
diff --git a/config/routes.rb b/config/routes.rb
index 6bb0d31df2e672fe1a35a268be160a53bd4a7f61..b1f34700c00f56478194a6b70cb1e6a72a096897 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,5 @@
 require 'sidekiq/web'
+require 'sidekiq/cron/web'
 require 'api/api'
 
 Rails.application.routes.draw do
@@ -368,7 +369,7 @@ Rails.application.routes.draw do
       end
 
       resource :avatar, only: [:destroy]
-      resources :milestones, only: [:index, :show, :update, :new, :create]
+      resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
     end
   end
 
diff --git a/config/schedule.yml b/config/schedule.yml
new file mode 100644
index 0000000000000000000000000000000000000000..993a95fef565b87d049e4d6e15e3e0ed7c33ee2e
--- /dev/null
+++ b/config/schedule.yml
@@ -0,0 +1,10 @@
+# Here is a list of jobs that are scheduled to run periodically.
+# We use a UNIX cron notation to specify execution schedule.
+#
+# Please read here for more information:
+# https://github.com/ondrejbartas/sidekiq-cron#adding-cron-job
+
+stuck_ci_builds_worker:
+  cron: "0 0 * * *"
+  class: "StuckCiBuildsWorker"
+  queue: "default"
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 64e52eba3a2240574240a6351c8f3b6d930bf42b..1feae62b1c7dbbfd99b970bacbf9250f2d0de452 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -190,7 +190,7 @@ This will create two service containers (MySQL and PostgreSQL).
 
 1. Create a build container and execute script in its context:
 ```
-$ cat build_script | docker run -n build -i -l mysql:service-mysql -l postgres:service-postgres ruby:2.1 /bin/bash
+$ docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.1 /bin/bash < build_script
 ```
 This will create build container that has two service containers linked.
 The build_script is piped using STDIN to bash interpreter which executes the build script in container. 
diff --git a/doc/release/README.md b/doc/release/README.md
index 1342b90f3b3c49888398bef85726fbb24cd31f57..52eca7c02a61e7c2043893f4fc3115f65c52e85d 100644
--- a/doc/release/README.md
+++ b/doc/release/README.md
@@ -1,4 +1,8 @@
-GitLab has the following updates:
+## Release cycle
+
+Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases are published when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG).  Features that will likely be in the next releases can be found on the [direction page](https://about.gitlab.com/direction/).
+
+## Release process documentation
 
 - [Monthly release](monthly.md), every month on the 22nd.
 - [Patch release](patch.md), if there are serious regressions.
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 7d838187a265fd7b2889e76409105ea70440ae66..03746dd9df37a89a1c18873b15ee49709c7a4a4d 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -57,6 +57,9 @@ X-Gitlab-Event: Push Hook
         "name": "Jordi Mallach",
         "email": "jordi@softcatala.org"
       }
+      "added": ["CHANGELOG"],
+      "modified": ["app/controller/application.rb"],
+      "removed": []
     },
     {
       "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
@@ -66,13 +69,14 @@ X-Gitlab-Event: Push Hook
       "author": {
         "name": "GitLab dev user",
         "email": "gitlabdev@dv6700.(none)"
-      }
+      },
+      "added": ["CHANGELOG"],
+      "modified": ["app/controller/application.rb"],
+      "removed": []
     }
   ],
-  "total_commits_count": 4,
-  "added": ["CHANGELOG"],
-  "modified": ["app/controller/application.rb"],
-  "removed": []
+  "total_commits_count": 4
+  
 }
 ```
 
diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature
index 4a2b870e0822987798d4c8b238d6122a5b9a1142..d6e0c84537e89b13160c5479d491d32c57666fda 100644
--- a/features/project/commits/diff_comments.feature
+++ b/features/project/commits/diff_comments.feature
@@ -13,6 +13,12 @@ Feature: Project Commits Diff Comments
     Given I leave a diff comment like "Typo, please fix"
     Then I should see a diff comment saying "Typo, please fix"
 
+  @javascript
+  Scenario: I can add a diff comment with a single emoji
+    Given I open a diff comment form
+    And I write a diff comment like ":smile:"
+    Then I should see a diff comment with an emoji image
+
   @javascript
   Scenario: I get a temporary form for the first comment on a diff line
     Given I open a diff comment form
diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature
index a9bc8ffb9bb85ff245295621c0b46937f0801628..2609f129d0780706fa5f234e87c8be7fe6e97835 100644
--- a/features/project/issues/award_emoji.feature
+++ b/features/project/issues/award_emoji.feature
@@ -11,4 +11,8 @@ Feature: Award Emoji
     And I click to emoji in the picker
     Then I have award added
     And I can remove it by clicking to icon
-    
\ No newline at end of file
+
+  @javascript
+  Scenario: I add award emoji using regular comment
+  Given I leave comment with a single emoji
+  Then I have award added
diff --git a/features/project/merge_requests/accept.feature b/features/project/merge_requests/accept.feature
index 3e6e59a3808b379625cdd66f84d221928e83a647..9bc2b7c8ecac5289826ca32cf57126647c9db59c 100644
--- a/features/project/merge_requests/accept.feature
+++ b/features/project/merge_requests/accept.feature
@@ -8,10 +8,12 @@ Feature: Project Merge Requests Acceptance
     Given I am on the Merge Request detail page
     When I click on "Remove source branch" option
     And I click on Accept Merge Request
-    Then I should not see the Remove Source Branch button
+    Then I should see merge request merged
+    And I should not see the Remove Source Branch button
 
   @javascript
   Scenario: Accepting the Merge Request without removing the source branch
     Given I am on the Merge Request detail page
     When I click on Accept Merge Request
-    Then I should see the Remove Source Branch button
+    Then I should see merge request merged
+    And I should see the Remove Source Branch button
diff --git a/features/steps/admin/labels.rb b/features/steps/admin/labels.rb
index 2ea5dffdc661aa5c6bb9aa0e1f5e80e41cb72669..55ddcc25085f3046d860ddf4587f8dc173b8664b 100644
--- a/features/steps/admin/labels.rb
+++ b/features/steps/admin/labels.rb
@@ -71,7 +71,7 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
 
   step 'I should see label color error message' do
     page.within '.label-form' do
-      expect(page).to have_content 'Color is invalid'
+      expect(page).to have_content 'Color must be a valid color code'
     end
   end
 
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index df4a23a37169a5759a18e842980100b214518110..be4db770948cc3391592c4a79251930be02f943a 100644
--- a/features/steps/project/hooks.rb
+++ b/features/steps/project/hooks.rb
@@ -70,8 +70,6 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
 
   step 'I should see hook service down error message' do
     expect(page).to have_selector '.flash-alert',
-                              text: 'Hook execution failed. '\
-                                    'Ensure hook URL is correct and '\
-                                    'service is up.'
+                              text: 'Hook execution failed: Exception from'
   end
 end
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 8f7a45dec0eb0a77137e36264569a84c6fd11128..325eaf2ea6a5834ea388eed6e1244e152b372a88 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -9,33 +9,40 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
   end
 
   step 'I click to emoji-picker' do
-    page.within ".awards-controls" do
-      page.find(".add-award").click
+    page.within '.awards-controls' do
+      page.find('.add-award').click
     end
   end
 
   step 'I click to emoji in the picker' do
-    page.within ".awards-menu" do
-      page.first("img").click
+    page.within '.awards-menu' do
+      page.first('img').click
     end
   end
 
   step 'I can remove it by clicking to icon' do
-    page.within ".awards" do
-      page.first(".award").click
-      expect(page).to_not have_selector ".award"
+    page.within '.awards' do
+      page.first('.award').click
+      expect(page).to_not have_selector '.award'
     end
   end
 
   step 'I have award added' do
-    page.within ".awards" do
-      expect(page).to have_selector ".award"
-      expect(page.find(".award .counter")).to have_content "1"
+    page.within '.awards' do
+      expect(page).to have_selector '.award'
+      expect(page.find('.award .counter')).to have_content '1'
     end
   end
 
   step 'project "Shop" has issue "Bugfix"' do
-    @project = Project.find_by(name: "Shop")
-    @issue = create(:issue, title: "Bugfix", project: project)
+    @project = Project.find_by(name: 'Shop')
+    @issue = create(:issue, title: 'Bugfix', project: project)
+  end
+
+  step 'I leave comment with a single emoji' do
+    page.within('.js-main-target-form') do
+      fill_in 'note[note]', with: ':smile:'
+      click_button 'Add Comment'
+    end
   end
 end
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index e273bb391b3366f094eed70327dbd7e83a058ffb..2ab8956867bd780ba1e83b323bc36baa3aa57bb8 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -55,7 +55,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
 
   step 'I should see label color error message' do
     page.within '.label-form' do
-      expect(page).to have_content 'Color is invalid'
+      expect(page).to have_content 'Color must be a valid color code'
     end
   end
 
diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb
index 6adecaa83855f70d93b2a9c3837172e6df4cd04b..383c055c4efeb7926a7164e65eb557afc4b0563e 100644
--- a/features/steps/project/merge_requests/acceptance.rb
+++ b/features/steps/project/merge_requests/acceptance.rb
@@ -32,4 +32,8 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
   step 'I am signed in as a developer of the project' do
     login_as(@user)
   end
+
+  step 'I should see merge request merged' do
+    expect(page).to have_content('The changes were merged into')
+  end
 end
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 72621911a37151cd82b412d46d78465b3abb7380..dd466cde28d0efb5fa6a2c0bfab6cdea68053fee 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -87,6 +87,17 @@ module SharedDiffNote
     end
   end
 
+  step 'I write a diff comment like ":smile:"' do
+    page.within(diff_file_selector) do
+      click_diff_line(sample_commit.line_code)
+
+      page.within("form[rel$='#{sample_commit.line_code}']") do
+        fill_in 'note[note]', with: ':smile:'
+        click_button('Add Comment')
+      end
+    end
+  end
+
   step 'I submit the diff comment' do
     page.within(diff_file_selector) do
       click_button("Add Comment")
@@ -197,6 +208,12 @@ module SharedDiffNote
     end
   end
 
+  step 'I should see a diff comment with an emoji image' do
+    page.within("#{diff_file_selector} .note") do
+      expect(page).to have_xpath("//img[@alt=':smile:']")
+    end
+  end
+
   step 'I click side-by-side diff button' do
     find('#parallel-diff-btn').trigger('click')
   end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 2b4ada6e2ebbf2e3861128d0788b0adf78284308..6928fe0eb9da55fbcf3f9df3d0d290ba40827985 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -7,8 +7,12 @@ module API
       helpers do
         def map_public_to_visibility_level(attrs)
           publik = attrs.delete(:public)
-          publik = parse_boolean(publik)
-          attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true
+          if publik.present? && !attrs[:visibility_level].present?
+            publik = parse_boolean(publik)
+            # Since setting the public attribute to private could mean either
+            # private or internal, use the more conservative option, private.
+            attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
+          end
           attrs
         end
       end
diff --git a/lib/gitlab/blacklist.rb b/lib/gitlab/blacklist.rb
deleted file mode 100644
index 43145e0ee1b7f2a96b45840678dc1485a47acd1b..0000000000000000000000000000000000000000
--- a/lib/gitlab/blacklist.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Gitlab
-  module Blacklist
-    extend self
-
-    def path
-      %w(
-        admin
-        dashboard
-        files
-        groups
-        help
-        profile
-        projects
-        search
-        public
-        assets
-        u
-        s
-        teams
-        merge_requests
-        issues
-        users
-        snippets
-        services
-        repository
-        hooks
-        notes
-        unsubscribes
-        all
-        ci
-      )
-    end
-  end
-end
diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
index fa068d507634a1351985bab5e2877dfd8484e1e3..4f9cdef3869e5b53743467a97e83c0ef07174bb2 100644
--- a/lib/gitlab/push_data_builder.rb
+++ b/lib/gitlab/push_data_builder.rb
@@ -18,10 +18,7 @@ module Gitlab
       #     homepage: String,
       #   },
       #   commits: Array,
-      #   total_commits_count: Fixnum,
-      #   added: ["CHANGELOG"],
-      #   modified: [],
-      #   removed: ["tmp/file.txt"]
+      #   total_commits_count: Fixnum
       # }
       #
       def build(project, user, oldrev, newrev, ref, commits = [], message = nil)
@@ -33,11 +30,12 @@ module Gitlab
 
         # For performance purposes maximum 20 latest commits
         # will be passed as post receive hook data.
-        commit_attrs = commits_limited.map(&:hook_attrs)
+        commit_attrs = commits_limited.map do |commit|
+          commit.hook_attrs(with_changed_files: true)
+        end
 
         type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push"
 
-        repo_changes = repo_changes(project, newrev, oldrev)
         # Hash to be passed as post_receive_data
         data = {
           object_kind: type,
@@ -60,10 +58,7 @@ module Gitlab
             visibility_level: project.visibility_level
           },
           commits: commit_attrs,
-          total_commits_count: commits_count,
-          added: repo_changes[:added],
-          modified: repo_changes[:modified],
-          removed: repo_changes[:removed]
+          total_commits_count: commits_count
         }
 
         data
@@ -94,27 +89,6 @@ module Gitlab
           newrev
         end
       end
-
-      def repo_changes(project, newrev, oldrev)
-        changes = { added: [], modified: [], removed: [] }
-        compare_result = CompareService.new.
-          execute(project, newrev, project, oldrev)
-
-        if compare_result
-          compare_result.diffs.each do |diff|
-            case true
-            when diff.deleted_file
-              changes[:removed] << diff.old_path
-            when diff.renamed_file, diff.new_file
-              changes[:added] << diff.new_path
-            else
-              changes[:modified] << diff.new_path
-            end
-          end
-        end
-
-        changes
-      end
     end
   end
 end
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 016f7a536fb854e38adc1fbc9083e8e7f93f0c9d..79fe1474821293ab2be6c743d8b679633141f9cd 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -56,7 +56,7 @@ server {
   listen [::]:80 ipv6only=on default_server;
   server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
   server_tokens off; ## Don't show the nginx version number, a security best practice
-  return 301 https://$server_name$request_uri;
+  return 301 https://$http_host$request_uri;
   access_log  /var/log/nginx/gitlab_access.log;
   error_log   /var/log/nginx/gitlab_error.log;
 }
diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb
index 5337a69e84b34c8a0a0848347a16d8569919ea36..7793bf1e42170af64133c6de6d57a5447220dae3 100644
--- a/spec/controllers/commit_controller_spec.rb
+++ b/spec/controllers/commit_controller_spec.rb
@@ -110,6 +110,26 @@ describe Projects::CommitController do
         expect(response.body).to match(/^diff --git/)
       end
     end
+
+    context 'commit that removes a submodule' do
+      render_views
+
+      let(:fork_project) { create(:forked_project_with_submodules) }
+      let(:commit) { fork_project.commit('remove-submodule') }
+
+      before do
+        fork_project.team << [user, :master]
+      end
+
+      it 'renders it' do
+        get(:show,
+            namespace_id: fork_project.namespace.to_param,
+            project_id: fork_project.to_param,
+            id: commit.id)
+
+        expect(response).to be_success
+      end
+    end
   end
 
   describe "#branches" do
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eb0c6ac6d806b1d9d3d0d83a32794a98001991ed
--- /dev/null
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Groups::MilestonesController do
+  let(:group) { create(:group) }
+  let(:project) { create(:project, group: group) }
+  let(:project2) { create(:empty_project, group: group) }
+  let(:user)    { create(:user) }
+  let(:title) { '肯定不是中文的问题' }
+
+  before do
+    sign_in(user)
+    group.add_owner(user)
+    project.team << [user, :master]
+    controller.instance_variable_set(:@group, group)
+  end
+
+  describe "#create" do
+    it "should create group milestone with Chinese title" do
+      post :create,
+           group_id: group.id,
+           milestone: { project_ids: [project.id, project2.id], title: title }
+
+      expect(response).to redirect_to(group_milestone_path(group, title.to_slug.to_s, title: title))
+      expect(Milestone.where(title: title).count).to eq(2)
+    end
+  end
+end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 3e5e1fa87ae710b80cbddc2c40d63e7e8ecb56af..6aaec224f6eeff04ceaed2106978fa40201186ab 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -10,6 +10,30 @@ describe Projects::MergeRequestsController do
     project.team << [user, :master]
   end
 
+  describe '#new' do
+    context 'merge request that removes a submodule' do
+      render_views
+
+      let(:fork_project) { create(:forked_project_with_submodules) }
+
+      before do
+        fork_project.team << [user, :master]
+      end
+
+      it 'renders it' do
+        get :new,
+            namespace_id: fork_project.namespace.to_param,
+            project_id: fork_project.to_param,
+            merge_request: {
+              source_branch: 'remove-submodule',
+              target_branch: 'master'
+            }
+
+        expect(response).to be_success
+      end
+    end
+  end
+
   describe "#show" do
     shared_examples "export merge as" do |format|
       it "should generally work" do
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 8127efabe6e3e15e24ade7d24ec578f1f4b1eeda..d173bb350f1c66c0615d3114ffa5a4290e37ce87 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -5,7 +5,7 @@ describe Projects::MilestonesController do
   let(:user)    { create(:user) }
   let(:milestone) { create(:milestone, project: project) }
   let(:issue) { create(:issue, project: project, milestone: milestone) }
-  let(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
+  let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
 
   before do
     sign_in(user)
@@ -15,10 +15,9 @@ describe Projects::MilestonesController do
 
   describe "#destroy" do
     it "should remove milestone" do
-      merge_request.reload
       expect(issue.milestone_id).to eq(milestone.id)
 
-      delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.id, format: :js
+      delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid, format: :js
       expect(response).to be_success
 
       expect(Event.first.action).to eq(Event::DESTROYED)
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index d7cb3b2e86ead6487a8e965f03168475274c9c96..f0fc6916c4d2cdb6039a06ae98ca4f958b9c1e61 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
 
 describe 'Comments', feature: true do
   include RepoHelpers
+  include WaitForAjax
 
   describe 'On a merge request', js: true, feature: true do
     let!(:merge_request) { create(:merge_request) }
@@ -123,8 +124,8 @@ describe 'Comments', feature: true do
         it 'removes the attachment div and resets the edit form' do
           find('.js-note-attachment-delete').click
           is_expected.not_to have_css('.note-attachment')
-          expect(find('.current-note-edit-form', visible: false)).
-            not_to be_visible
+          is_expected.not_to have_css('.current-note-edit-form')
+          wait_for_ajax
         end
       end
     end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 0a64b70d6a67af089caf180d3f671d1b3f4e0a5d..5568f06639c42b83c147fe9052007ab2e3e26a84 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -278,7 +278,7 @@ describe ApplicationHelper do
       el = element.next_element
 
       expect(el.name).to eq 'script'
-      expect(el.text).to include "$('.js-timeago').timeago()"
+      expect(el.text).to include "$('.js-timeago').last().timeago()"
     end
 
     it 'allows the script tag to be excluded' do
diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb
index 02710742625d18a20242d63b5f721a9b8c5ac73e..2170399ab5c180fd22b761929d156abc3743c6e2 100644
--- a/spec/lib/gitlab/push_data_builder_spec.rb
+++ b/spec/lib/gitlab/push_data_builder_spec.rb
@@ -17,9 +17,9 @@ describe 'Gitlab::PushDataBuilder' do
     it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) }
     it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) }
     it { expect(data[:total_commits_count]).to eq(3) }
-    it { expect(data[:added]).to eq(["gitlab-grack"]) }
-    it { expect(data[:modified]).to eq([".gitmodules", "files/ruby/popen.rb", "files/ruby/regex.rb"]) }
-    it { expect(data[:removed]).to eq([]) }
+    it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) }
+    it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) }
+    it { expect(data[:commits].first[:removed]).to eq([]) }
   end
 
   describe :build do
@@ -38,8 +38,5 @@ describe 'Gitlab::PushDataBuilder' do
     it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
     it { expect(data[:commits]).to be_empty }
     it { expect(data[:total_commits_count]).to be_zero }
-    it { expect(data[:added]).to eq([]) }
-    it { expect(data[:modified]).to eq([]) }
-    it { expect(data[:removed]).to eq([]) }
   end
 end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index dfbac7b4004600c782713ec5f115f385c1617eb3..b67b84959d91592742d91fb53c2c28bfa9902e01 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -36,6 +36,22 @@ describe ApplicationSetting, models: true do
 
   it { expect(setting).to be_valid }
 
+  describe 'validations' do
+    let(:http)  { 'http://example.com' }
+    let(:https) { 'https://example.com' }
+    let(:ftp)   { 'ftp://example.com' }
+
+    it { is_expected.to allow_value(nil).for(:home_page_url) }
+    it { is_expected.to allow_value(http).for(:home_page_url) }
+    it { is_expected.to allow_value(https).for(:home_page_url) }
+    it { is_expected.not_to allow_value(ftp).for(:home_page_url) }
+
+    it { is_expected.to allow_value(nil).for(:after_sign_out_path) }
+    it { is_expected.to allow_value(http).for(:after_sign_out_path) }
+    it { is_expected.to allow_value(https).for(:after_sign_out_path) }
+    it { is_expected.not_to allow_value(ftp).for(:after_sign_out_path) }
+  end
+
   context 'restricted signup domains' do
     it 'set single domain' do
       setting.restricted_signup_domains_raw = 'example.com'
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index d80748f23a41f74baf8f07c83304f9b4d6bcda63..2b325f44f64abff8cbef7ba91bc1270855a6298b 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -20,6 +20,21 @@ describe BroadcastMessage do
 
   it { is_expected.to be_valid }
 
+  describe 'validations' do
+    let(:triplet) { '#000' }
+    let(:hex)     { '#AABBCC' }
+
+    it { is_expected.to allow_value(nil).for(:color) }
+    it { is_expected.to allow_value(triplet).for(:color) }
+    it { is_expected.to allow_value(hex).for(:color) }
+    it { is_expected.not_to allow_value('000').for(:color) }
+
+    it { is_expected.to allow_value(nil).for(:font) }
+    it { is_expected.to allow_value(triplet).for(:font) }
+    it { is_expected.to allow_value(hex).for(:font) }
+    it { is_expected.not_to allow_value('000').for(:font) }
+  end
+
   describe :current do
     it "should return last message if time match" do
       broadcast_message = create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow)
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 974b52c1833ab9fd01ee366019eb167ec4e9c628..38a3dc1f4a6502adcac4b7d8fb68c3954ce28337 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -107,4 +107,15 @@ eos
     # Include the subject in the repository stub.
     let(:extra_commits) { [subject] }
   end
+
+  describe '#hook_attrs' do
+    let(:data) { commit.hook_attrs(with_changed_files: true) }
+
+    it { expect(data).to be_a(Hash) }
+    it { expect(data[:message]).to include('Add submodule from gitlab.com') }
+    it { expect(data[:timestamp]).to eq('2014-02-27T11:01:38+02:00') }
+    it { expect(data[:added]).to eq(["gitlab-grack"]) }
+    it { expect(data[:modified]).to eq([".gitmodules"]) }
+    it { expect(data[:removed]).to eq([]) }
+  end
 end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 2fdc49f02ee26bee5043a558e835507f68244dfe..35042788c6554d434c19e07f048d77b3900e948e 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -71,5 +71,11 @@ describe ProjectHook do
 
       expect { @project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError)
     end
+
+    it "handles SSL exceptions" do
+      expect(WebHook).to receive(:post).and_raise(OpenSSL::SSL::SSLError.new('SSL error'))
+
+      expect(@project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
+    end
   end
 end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 319fa0a7c8d70cae60ade1943744b1b8c6581ea9..fa261e64c355143a3b3e6e5625b0bbe554909aa3 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -4,6 +4,7 @@ describe Repository do
   include RepoHelpers
 
   let(:repository) { create(:project).repository }
+  let(:user) { create(:user) }
 
   describe :branch_names_contains do
     subject { repository.branch_names_contains(sample_commit.id) }
@@ -99,5 +100,104 @@ describe Repository do
       it { expect(subject.startline).to eq(186) }
       it { expect(subject.data.lines[2]).to eq("  - Feature: Replace teams with group membership\n") }
     end
+
   end
+
+  describe :add_branch do
+    context 'when pre hooks were successful' do
+      it 'should run without errors' do
+        hook = double(trigger: true)
+        expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
+
+        expect { repository.add_branch(user, 'new_feature', 'master') }.not_to raise_error
+      end
+
+      it 'should create the branch' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true)
+
+        branch = repository.add_branch(user, 'new_feature', 'master')
+
+        expect(branch.name).to eq('new_feature')
+      end
+    end
+
+    context 'when pre hooks failed' do
+      it 'should get an error' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false)
+
+        expect do
+          repository.add_branch(user, 'new_feature', 'master')
+        end.to raise_error(GitHooksService::PreReceiveError)
+      end
+
+      it 'should not create the branch' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false)
+
+        expect do
+          repository.add_branch(user, 'new_feature', 'master')
+        end.to raise_error(GitHooksService::PreReceiveError)
+        expect(repository.find_branch('new_feature')).to be_nil
+      end
+    end
+  end
+
+  describe :rm_branch do
+    context 'when pre hooks were successful' do
+      it 'should run without errors' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true)
+
+        expect { repository.rm_branch(user, 'feature') }.not_to raise_error
+      end
+
+      it 'should delete the branch' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true)
+
+        expect { repository.rm_branch(user, 'feature') }.not_to raise_error
+
+        expect(repository.find_branch('feature')).to be_nil
+      end
+    end
+
+    context 'when pre hooks failed' do
+      it 'should get an error' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false)
+
+        expect do
+          repository.rm_branch(user, 'new_feature')
+        end.to raise_error(GitHooksService::PreReceiveError)
+      end
+
+      it 'should not delete the branch' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false)
+
+        expect do
+          repository.rm_branch(user, 'feature')
+        end.to raise_error(GitHooksService::PreReceiveError)
+        expect(repository.find_branch('feature')).not_to be_nil
+      end
+    end
+  end
+
+  describe :commit_with_hooks do
+    context 'when pre hooks were successful' do
+      it 'should run without errors' do
+        expect_any_instance_of(GitHooksService).to receive(:execute).and_return(true)
+
+        expect do
+          repository.commit_with_hooks(user, 'feature') { sample_commit.id }
+        end.not_to raise_error
+      end
+    end
+
+    context 'when pre hooks failed' do
+      it 'should get an error' do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false)
+
+        expect do
+          repository.commit_with_hooks(user, 'feature') { sample_commit.id }
+        end.to raise_error(GitHooksService::PreReceiveError)
+      end
+    end
+  end
+
 end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 4631b12faf1763f79547e1c9daf5e34e32466d0d..a0f78d3b336a804ca8af4431abe7814a9b8398ec 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -91,7 +91,23 @@ describe User do
   end
 
   describe 'validations' do
-    it { is_expected.to validate_presence_of(:username) }
+    describe 'username' do
+      it 'validates presence' do
+        expect(subject).to validate_presence_of(:username)
+      end
+
+      it 'rejects blacklisted names' do
+        user = build(:user, username: 'dashboard')
+
+        expect(user).not_to be_valid
+        expect(user.errors.values).to eq [['dashboard is a reserved name']]
+      end
+
+      it 'validates uniqueness' do
+        expect(subject).to validate_uniqueness_of(:username)
+      end
+    end
+
     it { is_expected.to validate_presence_of(:projects_limit) }
     it { is_expected.to validate_numericality_of(:projects_limit) }
     it { is_expected.to allow_value(0).for(:projects_limit) }
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index aff109a942469fe8d37b61ec2ed8a5a2ed0034b9..667f0dbea5cdf8ca34144af044ca56fddae9dcfc 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -47,7 +47,7 @@ describe API::API, api: true  do
            name: 'Foo',
            color: '#FFAA'
       expect(response.status).to eq(400)
-      expect(json_response['message']['color']).to eq(['is invalid'])
+      expect(json_response['message']['color']).to eq(['must be a valid color code'])
     end
 
     it 'should return 400 for too long color code' do
@@ -55,7 +55,7 @@ describe API::API, api: true  do
            name: 'Foo',
            color: '#FFAAFFFF'
       expect(response.status).to eq(400)
-      expect(json_response['message']['color']).to eq(['is invalid'])
+      expect(json_response['message']['color']).to eq(['must be a valid color code'])
     end
 
     it 'should return 400 for invalid name' do
@@ -151,12 +151,12 @@ describe API::API, api: true  do
       expect(json_response['message']['title']).to eq(['is invalid'])
     end
 
-    it 'should return 400 for invalid name' do
+    it 'should return 400 when color code is too short' do
       put api("/projects/#{project.id}/labels", user),
           name: 'label1',
           color: '#FF'
       expect(response.status).to eq(400)
-      expect(json_response['message']['color']).to eq(['is invalid'])
+      expect(json_response['message']['color']).to eq(['must be a valid color code'])
     end
 
     it 'should return 400 for too long color code' do
@@ -164,7 +164,7 @@ describe API::API, api: true  do
            name: 'Foo',
            color: '#FFAAFFFF'
       expect(response.status).to eq(400)
-      expect(json_response['message']['color']).to eq(['is invalid'])
+      expect(json_response['message']['color']).to eq(['must be a valid color code'])
     end
   end
 end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index c59ee7af8ab772ba0caaa4d1a801276f4bc1def7..24b765f4979671a3a4771575ee510c5173e94d32 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -742,6 +742,18 @@ describe API::API, api: true  do
         end
       end
 
+      it 'should update visibility_level from public to private' do
+        project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
+
+        project_param = { public: false }
+        put api("/projects/#{project3.id}", user), project_param
+        expect(response.status).to eq(200)
+        project_param.each_pair do |k, v|
+          expect(json_response[k.to_s]).to eq(v)
+        end
+        expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
+      end
+
       it 'should not update name to existing name' do
         project_param = { name: project3.name }
         put api("/projects/#{project.id}", user), project_param
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index a9ef2fe58858000a39fff1f1e3eb89899b2845ac..2f609c63330d9d1142efbea168ccfd2de48cd61f 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -153,7 +153,7 @@ describe API::API, api: true  do
       expect(json_response['message']['projects_limit']).
         to eq(['must be greater than or equal to 0'])
       expect(json_response['message']['username']).
-        to eq([Gitlab::Regex.send(:namespace_regex_message)])
+        to eq([Gitlab::Regex.namespace_regex_message])
     end
 
     it "shouldn't available for non admin users" do
@@ -296,7 +296,7 @@ describe API::API, api: true  do
       expect(json_response['message']['projects_limit']).
         to eq(['must be greater than or equal to 0'])
       expect(json_response['message']['username']).
-        to eq([Gitlab::Regex.send(:namespace_regex_message)])
+        to eq([Gitlab::Regex.namespace_regex_message])
     end
 
     context "with existing user" do
diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7e018d3c9febda10904b10889527fcae7db26108
--- /dev/null
+++ b/spec/services/git_hooks_service_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe GitHooksService do
+  include RepoHelpers
+
+  let(:user)    { create :user }
+  let(:project) { create :project }
+  let(:service) { GitHooksService.new }
+
+  before do
+    @blankrev = Gitlab::Git::BLANK_SHA
+    @oldrev = sample_commit.parent_id
+    @newrev = sample_commit.id
+    @ref = 'refs/heads/feature'
+    @repo_path = project.repository.path_to_repo
+  end
+
+  describe '#execute' do
+
+    context 'when receive hooks were successful' do
+      it 'should call post-receive hook' do
+        hook = double(trigger: true)
+        expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
+
+        expect(service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }).to eq(true)
+      end
+    end
+
+    context 'when pre-receive hook failed' do
+      it 'should not call post-receive hook' do
+        expect(service).to receive(:run_hook).with('pre-receive').and_return(false)
+        expect(service).not_to receive(:run_hook).with('post-receive')
+
+        expect do
+          service.execute(user, @repo_path, @blankrev, @newrev, @ref)
+        end.to raise_error(GitHooksService::PreReceiveError)
+      end
+    end
+
+    context 'when update hook failed' do
+      it 'should not call post-receive hook' do
+        expect(service).to receive(:run_hook).with('pre-receive').and_return(true)
+        expect(service).to receive(:run_hook).with('update').and_return(false)
+        expect(service).not_to receive(:run_hook).with('post-receive')
+
+        expect do
+          service.execute(user, @repo_path, @blankrev, @newrev, @ref)
+        end.to raise_error(GitHooksService::PreReceiveError)
+      end
+    end
+
+  end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 787670e929792e5080975af31f4b5dc50dd96cf0..78b9a0f42fa6dfb80594bba2778be67ac4e6c775 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -21,7 +21,8 @@ module TestEnv
   # We currently only need a subset of the branches
   FORKED_BRANCH_SHA = {
     'add-submodule-version-bump' => '3f547c08',
-    'master' => '5937ac0'
+    'master' => '5937ac0',
+    'remove-submodule' => '2a33e0c0'
   }
 
   # Test environment
diff --git a/spec/support/wait_for_ajax.rb b/spec/support/wait_for_ajax.rb
new file mode 100644
index 0000000000000000000000000000000000000000..692d219e9f1ffa16bd7023df43f4305c1d8f8492
--- /dev/null
+++ b/spec/support/wait_for_ajax.rb
@@ -0,0 +1,11 @@
+module WaitForAjax
+  def wait_for_ajax
+    Timeout.timeout(Capybara.default_wait_time) do
+      loop until finished_all_ajax_requests?
+    end
+  end
+
+  def finished_all_ajax_requests?
+    page.evaluate_script('jQuery.active').zero?
+  end
+end