diff --git a/CHANGELOG b/CHANGELOG
index 43a45e9ae5ba5e170a4f7b9574e2d1b408cb81be..bb5137df7d50876a2e93cff75f9d359ff2b70219 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,8 @@ v 7.5.0
   - Fix LDAP authentication for Git HTTP access
   - Fix LDAP config lookup for provider 'ldap'
   - Drop all sequences during Postgres database restore
+  - Add Atlassian Bamboo CI service (Drew Blessing)
+  - Mentioned @user will receive email even if he is not participating in issue or commit
 
 v 7.4.2
   - Fix internal snippet exposing for unauthenticated users
diff --git a/Gemfile b/Gemfile
index f6f3607cbd1beec163b0f85bb3d11ba2a9a4cb2a..a2314236e2916aae31c4e198098b7e0f1c26db85 100644
--- a/Gemfile
+++ b/Gemfile
@@ -37,7 +37,7 @@ gem "gitlab_git", '7.0.0.rc10'
 gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
 
 # LDAP Auth
-gem 'gitlab_omniauth-ldap', '1.1.0', require: "omniauth-ldap"
+gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap"
 
 # Git Wiki
 gem 'gollum-lib', '~> 3.0.0'
@@ -186,6 +186,7 @@ gem "gon", '~> 5.0.0'
 gem 'nprogress-rails'
 gem 'request_store'
 gem "virtus"
+gem 'addressable'
 
 group :development do
   gem "annotate", "~> 2.6.0.beta2"
diff --git a/Gemfile.lock b/Gemfile.lock
index 314884fa36ee40834b80a383cdb380f003513bc6..800f33590cb20df233f42fc07748927207922519 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -185,11 +185,11 @@ GEM
       gitlab-linguist (~> 3.0)
       rugged (~> 0.21.0)
     gitlab_meta (7.0)
-    gitlab_omniauth-ldap (1.1.0)
-      net-ldap (~> 0.7.0)
+    gitlab_omniauth-ldap (1.2.0)
+      net-ldap (~> 0.9)
       omniauth (~> 1.0)
       pyu-ruby-sasl (~> 0.0.3.1)
-      rubyntlm (~> 0.1.1)
+      rubyntlm (~> 0.3)
     gollum-lib (3.0.0)
       github-markup (~> 1.1.0)
       gitlab-grit (~> 2.6.5)
@@ -299,7 +299,7 @@ GEM
     multi_xml (0.5.5)
     multipart-post (1.2.0)
     mysql2 (0.3.16)
-    net-ldap (0.7.0)
+    net-ldap (0.9.0)
     net-scp (1.1.2)
       net-ssh (>= 2.6.5)
     net-ssh (2.8.0)
@@ -445,7 +445,7 @@ GEM
       rspec-expectations (~> 2.14.0)
       rspec-mocks (~> 2.14.0)
     ruby-progressbar (1.2.0)
-    rubyntlm (0.1.1)
+    rubyntlm (0.4.0)
     rubypants (0.2.0)
     rugged (0.21.0)
     safe_yaml (0.9.7)
@@ -592,6 +592,7 @@ DEPENDENCIES
   RedCloth
   ace-rails-ap
   acts-as-taggable-on
+  addressable
   annotate (~> 2.6.0.beta2)
   asciidoctor (= 0.1.4)
   awesome_print
@@ -626,7 +627,7 @@ DEPENDENCIES
   gitlab_emoji (~> 0.0.1.1)
   gitlab_git (= 7.0.0.rc10)
   gitlab_meta (= 7.0)
-  gitlab_omniauth-ldap (= 1.1.0)
+  gitlab_omniauth-ldap (= 1.2.0)
   gollum-lib (~> 3.0.0)
   gon (~> 5.0.0)
   grape (~> 0.6.1)
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index ff0d0bb32b957c29046664951fadf1fc7f36a22c..e9a28c1215933da2a27689ea056e9b4c3fd41559 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -18,6 +18,7 @@
 #= require jquery.turbolinks
 #= require turbolinks
 #= require bootstrap
+#= require password_strength
 #= require select2
 #= require raphael
 #= require g.raphael-min
@@ -63,7 +64,7 @@ window.extractLast = (term) ->
   return split( term ).pop()
 
 window.rstrip = (val) ->
-  return val.replace(/\s+$/, '')
+  return if val then val.replace(/\s+$/, '') else val
 
 # Disable button if text field is empty
 window.disableButtonIfEmptyField = (field_selector, button_selector) ->
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index d3e595ae7b246483c71d5144c8f54d3061752905..ec4b7ea42cf090137cd3ddd5f6a0980c4e6ef6c8 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -62,6 +62,7 @@ class Dispatcher
         new TeamMembers()
       when 'groups:members'
         new GroupMembers()
+        new UsersSelect()
       when 'groups:new', 'groups:edit', 'admin:groups:edit'
         new GroupAvatar()
       when 'projects:tree:show'
@@ -83,6 +84,8 @@ class Dispatcher
       when 'admin'
         new Admin()
         switch path[1]
+          when 'groups'
+            new UsersSelect()
           when 'projects'
             new NamespaceSelect()
       when 'dashboard'
@@ -99,6 +102,8 @@ class Dispatcher
             new ProjectNew()
           when 'show'
             new ProjectShow()
+          when 'issues', 'merge_requests'
+            new ProjectUsersSelect()
           when 'wikis'
             new Wikis()
             shortcut_handler = new ShortcutsNavigation()
@@ -107,6 +112,7 @@ class Dispatcher
             shortcut_handler = new ShortcutsNavigation()
           when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
             shortcut_handler = new ShortcutsNavigation()
+            new UsersSelect()
 
 
     # If we haven't installed a custom shortcut handler, install the default one
diff --git a/app/assets/javascripts/password_strength.js.coffee b/app/assets/javascripts/password_strength.js.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..825f56302661229a7ab620d68c22c57fe769db2f
--- /dev/null
+++ b/app/assets/javascripts/password_strength.js.coffee
@@ -0,0 +1,31 @@
+#= require pwstrength-bootstrap-1.2.2
+overwritten_messages =
+  wordSimilarToUsername: "Your password should not contain your username"
+
+overwritten_rules =
+  wordSequences: false
+
+options =
+  showProgressBar: false
+  showVerdicts: false
+  showPopover: true
+  showErrors: true
+  showStatus: true
+  errorMessages: overwritten_messages
+  
+$(document).ready ->
+  profileOptions = {}
+  profileOptions.ui = options
+  profileOptions.rules =
+    activated: overwritten_rules
+
+  deviseOptions = {}
+  deviseOptions.common =
+    usernameField: "#user_username"
+  deviseOptions.ui = options
+  deviseOptions.rules =
+    activated: overwritten_rules
+
+  $("#user_password_profile").pwstrength profileOptions
+  $("#user_password_sign_up").pwstrength deviseOptions
+  $("#user_password_recover").pwstrength deviseOptions
diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee
index cfbcd5108c83237dd3d7103ed4ba479df12e6296..7fb33926096c1b86e3490f7337ed821863b44bd0 100644
--- a/app/assets/javascripts/project_users_select.js.coffee
+++ b/app/assets/javascripts/project_users_select.js.coffee
@@ -1,6 +1,6 @@
-@projectUsersSelect =
-  init: ->
-    $('.ajax-project-users-select').each (i, select) ->
+class @ProjectUsersSelect
+  constructor: ->
+    $('.ajax-project-users-select').each (i, select) =>
       project_id = $(select).data('project-id') || $('body').data('project-id')
 
       $(select).select2
@@ -28,14 +28,16 @@
             Api.user(id, callback)
 
 
-        formatResult: projectUsersSelect.projectUserFormatResult
-        formatSelection: projectUsersSelect.projectUserFormatSelection
+        formatResult: (args...) =>
+          @formatResult(args...)
+        formatSelection: (args...) =>
+          @formatSelection(args...)
         dropdownCssClass: "ajax-project-users-dropdown"
         dropdownAutoWidth: true
         escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
           m
 
-  projectUserFormatResult: (user) ->
+  formatResult: (user) ->
     if user.avatar_url
       avatar = user.avatar_url
     else
@@ -52,8 +54,5 @@
        <div class='user-username'>#{user.username}</div>
      </div>"
 
-  projectUserFormatSelection: (user) ->
+  formatSelection: (user) ->
     user.name
-
-$ ->
-  projectUsersSelect.init()
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 86318bd7d94849e14fa2b7bacde4b24d3f13b1f7..9eee7406511230d0fff181110e2592af4467fa3b 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -1,5 +1,30 @@
-$ ->
-  userFormatResult = (user) ->
+class @UsersSelect
+  constructor: ->
+    $('.ajax-users-select').each (i, select) =>
+      $(select).select2
+        placeholder: "Search for a user"
+        multiple: $(select).hasClass('multiselect')
+        minimumInputLength: 0
+        query: (query) ->
+          Api.users query.term, (users) ->
+            data = { results: users }
+            query.callback(data)
+
+        initSelection: (element, callback) ->
+          id = $(element).val()
+          if id isnt ""
+            Api.user(id, callback)
+
+
+        formatResult: (args...) =>
+          @formatResult(args...)
+        formatSelection: (args...) =>
+          @formatSelection(args...)
+        dropdownCssClass: "ajax-users-dropdown"
+        escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
+          m
+
+  formatResult: (user) ->
     if user.avatar_url
       avatar = user.avatar_url
     else
@@ -11,27 +36,5 @@ $ ->
        <div class='user-username'>#{user.username}</div>
      </div>"
 
-  userFormatSelection = (user) ->
+  formatSelection: (user) ->
     user.name
-
-  $('.ajax-users-select').each (i, select) ->
-    $(select).select2
-      placeholder: "Search for a user"
-      multiple: $(select).hasClass('multiselect')
-      minimumInputLength: 0
-      query: (query) ->
-        Api.users query.term, (users) ->
-          data = { results: users }
-          query.callback(data)
-
-      initSelection: (element, callback) ->
-        id = $(element).val()
-        if id isnt ""
-          Api.user(id, callback)
-
-
-      formatResult: userFormatResult
-      formatSelection: userFormatSelection
-      dropdownCssClass: "ajax-users-dropdown"
-      escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
-        m
diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss
index 086875582f396c9fac6f540d93fb5f32c9766745..b9f4e317e9c7db053842d9aa7e2188dee026f97c 100644
--- a/app/assets/stylesheets/sections/profile.scss
+++ b/app/assets/stylesheets/sections/profile.scss
@@ -111,3 +111,20 @@
     height: 50px;
   }
 }
+
+//CSS for password-strength indicator
+#password-strength {
+  margin-bottom: 0;
+}
+
+.has-success input {
+  background-color: #D6F1D7 !important;
+}
+
+.has-error input {
+  background-color: #F3CECE !important;
+}
+
+.has-warning input {
+  background-color: #FFE9A4 !important;
+}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 548d5e4d4c7f708a997f4b4527774e501163eb02..f1e1bebe5ce548108e62173229d47e31e5bdce45 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -5,7 +5,6 @@ class ApplicationController < ActionController::Base
   before_filter :authenticate_user!
   before_filter :reject_blocked!
   before_filter :check_password_expiration
-  before_filter :add_abilities
   before_filter :ldap_security_check
   before_filter :default_headers
   before_filter :add_gon_variables
@@ -72,7 +71,7 @@ class ApplicationController < ActionController::Base
   end
 
   def abilities
-    @abilities ||= Six.new
+    Ability.abilities
   end
 
   def can?(object, action, subject)
@@ -113,10 +112,6 @@ class ApplicationController < ActionController::Base
     nil
   end
 
-  def add_abilities
-    abilities << Ability
-  end
-
   def authorize_project!(action)
     return access_denied! unless can?(current_user, action, project)
   end
diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb
index f8e1a31e0b325fc3014e9821fb54b99c55aee250..ada7031fea4318add194b9862790e63815e4f92c 100644
--- a/app/controllers/explore/groups_controller.rb
+++ b/app/controllers/explore/groups_controller.rb
@@ -1,7 +1,6 @@
 class Explore::GroupsController < ApplicationController
   skip_before_filter :authenticate_user!,
-                     :reject_blocked, :set_current_user_for_observers,
-                     :add_abilities
+                     :reject_blocked, :set_current_user_for_observers
 
   layout "explore"
 
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index b6fa8b7e3872659dd10d8fea4c734b0007f5d894..d75fd8e72fa6b3b14b80aab5782f5790d849c7c1 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -1,7 +1,6 @@
 class Explore::ProjectsController < ApplicationController
   skip_before_filter :authenticate_user!,
-    :reject_blocked,
-    :add_abilities
+                     :reject_blocked
 
   layout 'explore'
 
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index b50f62864592345b2e0e0cc9c7010d6bae7e51ce..a5f30dcfd9d2a553a01323a65390ff74281be790 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -41,7 +41,8 @@ class Projects::ServicesController < Projects::ApplicationController
     params.require(:service).permit(
       :title, :token, :type, :active, :api_key, :subdomain,
       :room, :recipients, :project_url, :webhook,
-      :user_key, :device, :priority, :sound
+      :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
+      :build_key
     )
   end
 end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 56c4f22120dd880095cebc61441c422978b12d53..d05742405112f3d8e6a78233739c026a52d2858f 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -48,7 +48,7 @@ class IssuableFinder
       else
         []
       end
-    elsif current_user && params[:authorized_only].presence
+    elsif current_user && params[:authorized_only].presence && !current_user_related?
       klass.of_projects(current_user.authorized_projects).references(:project)
     else
       klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project)
@@ -142,4 +142,8 @@ class IssuableFinder
   def project
     Project.where(id: params[:project_id]).first if params[:project_id].present?
   end
+
+  def current_user_related?
+    params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
+  end
 end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index e155abc1449cf7a9d4996baeb066d8d32cd484b1..97a72bf3635b17ae61d5fab57284c9fd632c7762 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -262,5 +262,13 @@ class Ability
       end
       rules
     end
+
+    def abilities
+      @abilities ||= begin
+                       abilities = Six.new
+                       abilities << self
+                       abilities
+                     end
+    end
   end
 end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 5938d9cb28eefee136e2a959dd50e4a481f60853..6c1aa99668a43c433a4a4ab2024731e624761658 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -52,11 +52,7 @@ module Mentionable
       if identifier == "all"
         users += project.team.members.flatten
       else
-        if has_project
-          id = project.team.members.find_by(username: identifier).try(:id)
-        else
-          id = User.find_by(username: identifier).try(:id)
-        end
+        id = User.find_by(username: identifier).try(:id)
         users << User.find(id) unless id.blank?
       end
     end
diff --git a/app/models/project.rb b/app/models/project.rb
index 613f98ba44bdfc3c22316a69a22ee5d27bde49bb..c58c9b551c9272d638c35a670070964a58735e38 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -65,6 +65,7 @@ class Project < ActiveRecord::Base
   has_one :gemnasium_service, dependent: :destroy
   has_one :slack_service, dependent: :destroy
   has_one :buildbox_service, dependent: :destroy
+  has_one :bamboo_service, dependent: :destroy
   has_one :pushover_service, dependent: :destroy
   has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
   has_one :forked_from_project, through: :forked_project_link
@@ -313,7 +314,7 @@ class Project < ActiveRecord::Base
   end
 
   def available_services_names
-    %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox)
+    %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox bamboo)
   end
 
   def gitlab_ci?
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b9eec9ab21e9a9f28028b43cfb32ba4962b7597c
--- /dev/null
+++ b/app/models/project_services/bamboo_service.rb
@@ -0,0 +1,105 @@
+class BambooService < CiService
+  include HTTParty
+
+  prop_accessor :bamboo_url, :build_key, :username, :password
+
+  validates :bamboo_url, presence: true,
+            format: { with: URI::regexp }, if: :activated?
+  validates :build_key, presence: true, if: :activated?
+  validates :username, presence: true,
+            if: ->(service) { service.password? }, if: :activated?
+  validates :password, presence: true,
+            if: ->(service) { service.username? }, if: :activated?
+
+  attr_accessor :response
+
+  after_save :compose_service_hook, if: :activated?
+
+  def compose_service_hook
+    hook = service_hook || build_service_hook
+    hook.save
+  end
+
+  def title
+    'Atlassian Bamboo CI'
+  end
+
+  def description
+    'A continuous integration and build server'
+  end
+
+  def help
+    'You must set up automatic revision labeling and a repository trigger in Bamboo.'
+  end
+
+  def to_param
+    'bamboo'
+  end
+
+  def fields
+    [
+        { type: 'text', name: 'bamboo_url',
+          placeholder: 'Bamboo root URL like https://bamboo.example.com' },
+        { type: 'text', name: 'build_key',
+          placeholder: 'Bamboo build plan key like KEY' },
+        { type: 'text', name: 'username',
+          placeholder: 'A user with API access, if applicable' },
+        { type: 'password', name: 'password' },
+    ]
+  end
+
+  def build_info(sha)
+    url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
+
+    if username.blank? && password.blank?
+      @response = HTTParty.get(parsed_url.to_s, verify: false)
+    else
+      get_url = "#{url}&os_authType=basic"
+      auth = {
+          username: username,
+          password: password,
+      }
+      @response = HTTParty.get(get_url, verify: false, basic_auth: auth)
+    end
+  end
+
+  def build_page(sha)
+    build_info(sha) if @response.nil? || !@response.code
+
+    if @response.code != 200 || @response['results']['results']['size'] == '0'
+      # If actual build link can't be determined, send user to build summary page.
+      "#{bamboo_url}/browse/#{build_key}"
+    else
+      # If actual build link is available, go to build result page.
+      result_key = @response['results']['results']['result']['planResultKey']['key']
+      "#{bamboo_url}/browse/#{result_key}"
+    end
+  end
+
+  def commit_status(sha)
+    build_info(sha) if @response.nil? || !@response.code
+    return :error unless @response.code == 200 || @response.code == 404
+
+    status = if @response.code == 404 || @response['results']['results']['size'] == '0'
+               'Pending'
+             else
+               @response['results']['results']['result']['buildState']
+             end
+
+    if status.include?('Success')
+      'success'
+    elsif status.include?('Failed')
+      'failed'
+    elsif status.include?('Pending')
+      'pending'
+    else
+      :error
+    end
+  end
+
+  def execute(_data)
+    # Bamboo requires a GET and does not take any data.
+    self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}",
+                   verify: false)
+  end
+end
diff --git a/app/models/project_services/buildbox_service.rb b/app/models/project_services/buildbox_service.rb
index b0f8e28c97f06d0a7be7554954f66d603c887514..0ab67b79fe4fd85ebda1693d001a1f55ed3ef205 100644
--- a/app/models/project_services/buildbox_service.rb
+++ b/app/models/project_services/buildbox_service.rb
@@ -12,6 +12,8 @@
 #  properties :text
 #
 
+require "addressable/uri"
+
 class BuildboxService < CiService
   prop_accessor :project_url, :token
 
diff --git a/app/models/user.rb b/app/models/user.rb
index 42faea0070e9c497879e020dde9348ecbc5310ce..154cc0f3e16974afbe1b237479537067986c1cc7 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -330,11 +330,7 @@ class User < ActiveRecord::Base
   end
 
   def abilities
-    @abilities ||= begin
-                     abilities = Six.new
-                     abilities << Ability
-                     abilities
-                   end
+    Ability.abilities
   end
 
   def can_select_namespace?
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index ed286c0409585d2dd28ca317a71f008aec26583c..0d46eeaa18f11539eb8018ad47f861b26b7cbd6f 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -6,11 +6,7 @@ class BaseService
   end
 
   def abilities
-    @abilities ||= begin
-                     abilities = Six.new
-                     abilities << Ability
-                     abilities
-                   end
+    Ability.abilities
   end
 
   def can?(object, action, subject)
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 3678131427861fd9abd36d588f0fcb30c87cfe59..c9a1574b84e10daffc0a3d949b3411dcb9ea8c60 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -124,6 +124,7 @@ class NotificationService
     opts = { noteable_type: note.noteable_type, project_id: note.project_id }
 
     target = note.noteable
+
     if target.respond_to?(:participants)
       recipients = target.participants
     else
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 09105679bd2552103dd54307fde18cd1254869a7..1d7fef43184b5fabaeafda924f22a99f05b95e02 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -10,7 +10,7 @@
 = form_tag admin_groups_path, method: :get, class: 'form-inline' do
   .form-group
     = text_field_tag :name, params[:name], class: "form-control input-mn-300"
-  = submit_tag "Search", class: "btn submit btn-primary"
+  = button_tag "Search", class: "btn submit btn-primary"
 
 %hr
 
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 4494acc484218c198e50d74ef2b6c5eac59f4deb..8057de38805e7b2ce2fd5834c64d6fd3dff6d66e 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -64,7 +64,7 @@
           %div.prepend-top-10
             = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
           %hr
-          = submit_tag 'Add users into group', class: "btn btn-create"
+          = button_tag 'Add users into group', class: "btn btn-create"
     .panel.panel-default
       .panel-heading
         %h3.panel-title
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 5ca6090f8d3a0f439fb50ae356a9d88f0ce0b633..2cd6b12be7f47c6fc0ee17a6c16e494702cabdb2 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -35,7 +35,7 @@
                     = label
         %hr
         = hidden_field_tag :sort, params[:sort]
-        = submit_tag "Search", class: "btn submit btn-primary"
+        = button_tag "Search", class: "btn submit btn-primary"
         = link_to "Reset", admin_projects_path, class: "btn btn-cancel"
 
   .col-md-9
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index 1326cc0aac9ef167c4d9eb4369569ed368644dc8..f6cbf9b82ba4edfb24486384e1173fb1d76feb12 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -6,8 +6,8 @@
       .devise-errors
         = devise_error_messages!
       = f.hidden_field :reset_password_token
-      %div
-        = f.password_field :password, class: "form-control top", placeholder: "New password", required: true
+      .form-group#password-strength
+        = f.password_field :password, class: "form-control top", id: "user_password_recover", placeholder: "New password", required: true
       %div
         = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true
       .clearfix.append-bottom-10
diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml
index d6a952f3dc55245d017ba83d40780e1cd71ba373..123de881f594e914bbe23dbb51c08384a896773e 100644
--- a/app/views/devise/registrations/new.html.haml
+++ b/app/views/devise/registrations/new.html.haml
@@ -11,8 +11,8 @@
         = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
       %div
         = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
-      %div
-        = f.password_field :password, class: "form-control middle", placeholder: "Password", required: true
+      .form-group#password-strength
+        = f.password_field :password, class: "form-control middle", id: "user_password_sign_up", placeholder: "Password", required: true
       %div
         = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm password", required: true
       %div
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 01584611493a42e3c7ecdfba69b8dda794ceda7e..bf8a593c254e65753763ca6e034b2e7e3169da87 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -2,4 +2,4 @@
   = text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"}
   = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
   %br/
-  = submit_tag "LDAP Sign in", class: "btn-save btn"
+  = button_tag "LDAP Sign in", class: "btn-save btn"
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index c8243ff782cecd194cc172cbe70c7cb925262acc..709d062df8351e15f0273d995aceb801dac1db75 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -4,7 +4,7 @@
       .form-group
         = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search"
       .form-group
-        = submit_tag 'Search', class: "btn btn-primary wide"
+        = button_tag 'Search', class: "btn btn-primary wide"
 
   .pull-right
     .dropdown.inline
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index c8bf78385e80775c5db8b0289046e339794603fe..f797c4e3830d0250f57fdae04c391c1c2743371e 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -4,7 +4,7 @@
       .form-group
         = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search"
       .form-group
-        = submit_tag 'Search', class: "btn btn-primary wide"
+        = button_tag 'Search', class: "btn btn-primary wide"
 
   .pull-right
     .dropdown.inline
diff --git a/app/views/groups/members.html.haml b/app/views/groups/members.html.haml
index ba554cd5ef16fee4f85b29ebeee499b61b8ff66f..d2ebcdab7e1ec4a9974b2a69ffcf501c709956d5 100644
--- a/app/views/groups/members.html.haml
+++ b/app/views/groups/members.html.haml
@@ -13,7 +13,7 @@
   = form_tag members_group_path(@group), method: :get, class: 'form-inline member-search-form'  do
     .form-group
       = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' }
-    = submit_tag 'Search', class: 'btn'
+    = button_tag 'Search', class: 'btn'
 
   - if current_user && current_user.can?(:manage_group, @group)
     .pull-right
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 5ab82122ad762a8e006661030b43e3b8271008dd..2460a6a014d9103bd045f158720602e0dd5c2597 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -8,7 +8,7 @@
     - if @snippet || @snippets
       = hidden_field_tag :snippets, true
     = hidden_field_tag :repository_ref, @ref
-    = submit_tag 'Go' if ENV['RAILS_ENV'] == 'test'
+    = button_tag 'Go' if ENV['RAILS_ENV'] == 'test'
     .search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
 
 :javascript
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 2a7d317aa3e00eb787b4bde7d00a9fbb3838b413..425200ff523120d225cc10bb11d6207a4b123092 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -24,7 +24,7 @@
       .form-group
         = f.label :password, 'New password', class: 'control-label'
         .col-sm-10
-          = f.password_field :password, required: true, class: 'form-control'
+          = f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile'
       .form-group
         = f.label :password_confirmation, class: 'control-label'
         .col-sm-10
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index aef7348fd203a8053dcb241cc37af2b09b20b138..42d2d0db29c4eac0e9bde4327fa8aceecbf72083 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -16,7 +16,7 @@
     .col-sm-10= f.password_field :current_password, required: true, class: 'form-control'
   .form-group
     = f.label :password, class: 'control-label'
-    .col-sm-10= f.password_field :password, required: true, class: 'form-control'
+    .col-sm-10= f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile'
   .form-group
     = f.label :password_confirmation, class: 'control-label'
     .col-sm-10
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index da84dc4b6cf77d95704df39469441d82d4ce5beb..c5568315cb1002b8719fe9bf6373e888761e7a9d 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -15,7 +15,7 @@
           .form-group
             .col-sm-2
             .col-sm-10
-              = submit_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
+              = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
               = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
 
 :javascript
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index f5a530d95f22655df34d97cf32a123a83dc4cc3f..a6623240da1e393195dae245916d3759f92e4ca8 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -15,7 +15,7 @@
     .col-sm-10
       = text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control'
   .form-actions
-    = submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3
+    = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
     = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
 
 :javascript
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index da6157cf1b615789e28868b91856a774c0b8c0ef..cb0a3747f7d091122d054fb3a27ff4f8ccec60a8 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -12,7 +12,7 @@
         %span.input-group-addon to
         = text_field_tag :to, params[:to], class: "form-control"
     &nbsp;
-    = submit_tag "Compare", class: "btn btn-create commits-compare-btn"
+    = button_tag "Compare", class: "btn btn-create commits-compare-btn"
     - if compare_to_mr_button?
       = link_to compare_mr_path, class: 'prepend-left-10 btn' do
         %strong Make a merge request
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index f48f4bb295370785c593078d9d929889670e3360..b85cf7d8d3777202f361161a6507274db651e5fb 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -13,7 +13,7 @@
               = f.label :name, class: 'control-label' do
                 Project name
               .col-sm-10
-                = f.text_field :name, placeholder: "Example Project", class: "form-control"
+                = f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit"
 
 
             .form-group
@@ -124,6 +124,12 @@
         .errors-holder
         .panel-body
           = form_for(@project, html: { class: 'form-horizontal' }) do |f|
+            .form-group.project_name_holder
+              = f.label :name, class: 'control-label' do
+                Project name
+              .col-sm-9
+                .form-group
+                  = f.text_field :name, placeholder: "Example Project", class: "form-control"
             .form-group
               = f.label :path, class: 'control-label' do
                 %span Path
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 16d59d1fe9d6fce4e8fe17555718604d0f5e1236..1151f22c7e8e78076fba96fd25754c9412b0c01d 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -28,7 +28,7 @@
 
   - @service.fields.each do |field|
     - name = field[:name]
-    - value = @service.send(name)
+    - value = @service.send(name) unless field[:type] == 'password'
     - type = field[:type]
     - placeholder = field[:placeholder]
     - choices = field[:choices]
@@ -45,6 +45,8 @@
           = f.check_box name
         - elsif type == 'select'
           = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
+        - elsif type == 'password'
+          = f.password_field name, class: 'form-control'
 
   .form-actions
     = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index aa08b3977639e6054d7c4f50f48820bf102abbe7..ad7ff8d3db8c2b7f50bdd3b11c8f5b300a402c6c 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -21,7 +21,7 @@
       = text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control'
       .light (Optional) Entering a message will create an annotated tag.
   .form-actions
-    = submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3
+    = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
     = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel'
 
 :javascript
diff --git a/app/views/projects/team_members/import.html.haml b/app/views/projects/team_members/import.html.haml
index 510b579fe2f878025ffc410211d39342684e6539..d1f46c61b2e7d869fcce287a8fce9cf3ab3d6610 100644
--- a/app/views/projects/team_members/import.html.haml
+++ b/app/views/projects/team_members/import.html.haml
@@ -9,6 +9,6 @@
     .col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
 
   .form-actions
-    = submit_tag 'Import project members', class: "btn btn-create"
+    = button_tag 'Import project members', class: "btn btn-create"
     = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
 
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index bae57917a4c26d129ecda1d76e74ef7566432067..5b4816e4c40d38129309d3b7abca456065d2b95e 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -6,7 +6,7 @@
       .col-sm-6
         = search_field_tag :search, params[:search], placeholder: "issue 143", class: "form-control search-text-input", id: "dashboard_search"
       .col-sm-4
-        = submit_tag 'Search', class: "btn btn-create"
+        = button_tag 'Search', class: "btn btn-create"
     .form-group
       .col-sm-2
       - unless params[:snippets].eql? 'true'
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 2ca6abac57626074c3de8b4d1ee633cffcd1b085..bb0ffae0b708049abbb297712c47594f8999b4eb 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -39,6 +39,8 @@ production: &base
     # time_zone: 'UTC'
 
     ## Email settings
+    # Uncomment and set to false if you need to disable email sending from GitLab (default: true)
+    # email_enabled: true
     # Email address used in the "From" field in mails sent by GitLab
     email_from: example@example.com
 
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 4670791ddb084c54d1d8ff7e3293c0390367499c..27bb83784bab38955381647d98fcc6afdbc3ebf9 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -95,6 +95,7 @@ Settings.gitlab['https']        = false if Settings.gitlab['https'].nil?
 Settings.gitlab['port']       ||= Settings.gitlab.https ? 443 : 80
 Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
 Settings.gitlab['protocol']   ||= Settings.gitlab.https ? "https" : "http"
+Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil?
 Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}"
 Settings.gitlab['url']        ||= Settings.send(:build_gitlab_url)
 Settings.gitlab['user']       ||= 'git'
diff --git a/config/initializers/disable_email_interceptor.rb b/config/initializers/disable_email_interceptor.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c76a6b8b19f51dbd1a6855eb171cc0b725e58863
--- /dev/null
+++ b/config/initializers/disable_email_interceptor.rb
@@ -0,0 +1,2 @@
+# Interceptor in lib/disable_email_interceptor.rb
+ActionMailer::Base.register_interceptor(DisableEmailInterceptor) unless Gitlab.config.gitlab.email_enabled
diff --git a/doc/README.md b/doc/README.md
index a8e21f757143ae4eb432a05da298fa978786d967..7343d5ae27366d0acc4ab9b8f661a528dd545b2e 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -5,6 +5,7 @@
 - [API](api/README.md) Explore how you can access GitLab via a simple and powerful API.
 - [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system.
 - [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
+- [Project Services](project_services/project_services.md) Explore how project services can integrate a project with external services, such as for CI.
 - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project.
 - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
 - [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 7a39f2eec9f7a6d051e4eb9a14c2e2d084c7d903..ac6535b0c8633cb5734bf492038edadf4d3b091d 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -150,6 +150,17 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
 
     # Enable Redis socket for default Debian / Ubuntu path
     echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
+    # Grant permission to the socket to all members of the redis group
+    echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf
+
+    # Create the directory which contains the socket
+    mkdir /var/run/redis
+    chown redis:redis /var/run/redis
+    chmod 755 /var/run/redis
+    # Persist the directory which contains the socket, if applicable
+    if [ -d /etc/tmpfiles.d ]; then
+      echo 'd  /var/run/redis  0755  redis  redis  10d  -' | sudo tee -a /etc/tmpfiles.d/redis.conf
+    fi
 
     # Activate the changes to redis.conf
     sudo service redis-server restart
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 3e4c6a28c0e38744aec2df8aa7b10a30aeb167d2..ed19425314869ea6d2a4feed974ff581db40e95a 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -24,7 +24,7 @@ For the installations options please see [the installation page on the GitLab we
 On the above unsupported distributions is still possible to install GitLab yourself.
 Please see the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information.
 
-### Non Unix operating systems such as Windows
+### Non-Unix operating systems such as Windows
 
 GitLab is developed for Unix operating systems.
 GitLab does **not** run on Windows and we have no plans of supporting it in the near future.
@@ -53,8 +53,8 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
 - 512MB is the absolute minimum but we do not recommend this amount of memory.
 You will either need to configure 512MB or 1.5GB of swap space.
 With 512MB of swap space you must configure only one unicorn worker.
-With one unicorn worker only git over ssh access will work because the git over http access requires two running workers (one worker to receive the user request and one worker for the authorization check).
-If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow http access but it will still be slow.
+With one unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check).
+If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow HTTP access but it will still be slow.
 - 1GB RAM + 1GB swap supports up to 100 users
 - **2GB RAM** is the **recommended** memory size and supports up to 500 users
 - 4GB RAM supports up to 2,000 users
@@ -67,7 +67,7 @@ Notice: The 25 workers of Sidekiq will show up as separate processes in your pro
 
 ### Storage
 
-The necessary hard drive space largely depends on the size of the repos you want to store in GitLab. But as a *rule of thumb* you should have at least twice as much free space as your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
+The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
 
 If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
 
@@ -90,7 +90,7 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
 ## Supported web browsers
 
 - Chrome (Latest stable version)
-- Firefox (Latest released version) 
+- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/)) 
 - Safari 7+ (known problem: required fields in html5 do not work)
 - Opera (Latest released version)
 - IE 10+
diff --git a/doc/project_services/bamboo.md b/doc/project_services/bamboo.md
new file mode 100644
index 0000000000000000000000000000000000000000..51668128c6245cf8222875a15cc2bb8084460ded
--- /dev/null
+++ b/doc/project_services/bamboo.md
@@ -0,0 +1,60 @@
+# Atlassian Bamboo CI Service
+
+GitLab provides integration with Atlassian Bamboo for continuous integration.
+When configured, pushes to a project will trigger a build in Bamboo automatically.
+Merge requests will also display CI status showing whether the build is pending,
+failed, or completed successfully. It also provides a link to the Bamboo build
+page for more information.
+
+Bamboo doesn't quite provide the same features as a traditional build system when
+it comes to accepting webhooks and commit data. There are a few things that
+need to be configured in a Bamboo build plan before GitLab can integrate.
+
+## Setup
+
+### Complete these steps in Bamboo:
+
+1. Navigate to a Bamboo build plan and choose 'Configure plan' from the 'Actions'
+dropdown.
+1. Select the 'Triggers' tab.
+1. Click 'Add trigger'.
+1. Enter a description such as 'GitLab trigger'
+1. Choose 'Repository triggers the build when changes are committed'
+1. Check one or more repositories checkboxes
+1. Enter the GitLab IP address in the 'Trigger IP addresses' box. This is a 
+whitelist of IP addresses that are allowed to trigger Bamboo builds.
+1. Save the trigger.
+1. In the left pane, select a build stage. If you have multiple build stages 
+you want to select the last stage that contains the git checkout task.
+1. Select the 'Miscellaneous' tab.
+1. Under 'Pattern Match Labelling' put '${bamboo.repository.revision.number}' 
+in the 'Labels' box.
+1. Save
+
+Bamboo is now ready to accept triggers from GitLab. Next, set up the Bamboo
+service in GitLab
+
+### Complete these steps in GitLab:
+
+1. Navigate to the project you want to configure to trigger builds.
+1. Select 'Settings' in the top navigation.
+1. Select 'Services' in the left navigation.
+1. Click 'Atlassian Bamboo CI'
+1. Select the 'Active' checkbox.
+1. Enter the base URL of your Bamboo server. 'https://bamboo.example.com'
+1. Enter the build key from your Bamboo build plan. Build keys are a short, 
+all capital letter, identifier that is unique. It will be something like PR-BLD
+1. If necessary, enter username and password for a Bamboo user that has 
+access to trigger the build plan. Leave these fields blank if you do not require
+authentication.
+1. Save or optionally click 'Test Settings'. Please note that 'Test Settings'
+will actually trigger a build in Bamboo.
+
+## Troubleshooting
+
+If builds are not triggered, these are a couple of things to keep in mind.
+
+1. Ensure you entered the right GitLab IP address in Bamboo under 'Trigger
+IP addresses'.
+1. Remember that GitLab only triggers builds on push events. A commit via the
+web interface will not trigger CI currently.
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
new file mode 100644
index 0000000000000000000000000000000000000000..20a69a211dd664fa10c5f34b9f4df3637552531f
--- /dev/null
+++ b/doc/project_services/project_services.md
@@ -0,0 +1,18 @@
+# Project Services
+ 
+__Project integrations with external services for continuous integration and more.__
+
+## Services
+
+- Assemblia
+- [Atlassian Bamboo CI](bamboo.md) An Atlassian product for continous integration.
+- Build box
+- Campfire
+- Emails on push
+- Flowdock
+- Gemnasium
+- GitLab CI
+- Hipchat
+- PivotalTracker
+- Pushover
+- Slack
diff --git a/doc/release/patch.md b/doc/release/patch.md
index 3ee55028b1fdec609dfa02b0f40953e902f774b2..5d2fa053cac6a034a4936a652772f15e80fb900b 100644
--- a/doc/release/patch.md
+++ b/doc/release/patch.md
@@ -26,6 +26,6 @@ Otherwise include it in the monthly release and note there was a regression fix
 1. Apply the patch to GitLab Cloud and the private GitLab development server
 1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md)
 1. Cherry-pick the changelog update back into master
-1. Create blog post
-1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing as well as the link to the changelog
+1. Create and publish a blog post
+1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing and link to the blog post
 1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only)
diff --git a/doc/update/6.x-or-7.x-to-7.4.md b/doc/update/6.x-or-7.x-to-7.3.md
similarity index 95%
rename from doc/update/6.x-or-7.x-to-7.4.md
rename to doc/update/6.x-or-7.x-to-7.3.md
index 2fa6889af73f42cb423fd73e2f08c812374d83dc..66853634d38b4cf5200e5fa46b0485d71fe8c684 100644
--- a/doc/update/6.x-or-7.x-to-7.4.md
+++ b/doc/update/6.x-or-7.x-to-7.3.md
@@ -1,6 +1,6 @@
-# From 6.x or 7.x to 7.4
+# From 6.x or 7.x to 7.3
 
-This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.4.
+This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.3.
 
 ## Global issue numbers
 
@@ -70,7 +70,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut
 For GitLab Community Edition:
 
 ```bash
-sudo -u git -H git checkout 7-4-stable
+sudo -u git -H git checkout 7-3-stable
 ```
 
 OR
@@ -78,7 +78,7 @@ OR
 For GitLab Enterprise Edition:
 
 ```bash
-sudo -u git -H git checkout 7-4-stable-ee
+sudo -u git -H git checkout 7-3-stable-ee
 ```
 
 ## 4. Install additional packages
@@ -154,14 +154,14 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
 TIP: to see what changed in `gitlab.yml.example` in this release use next command:
 
 ```
-git diff 6-0-stable:config/gitlab.yml.example 7-4-stable:config/gitlab.yml.example
+git diff 6-0-stable:config/gitlab.yml.example 7-3-stable:config/gitlab.yml.example
 ```
 
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/unicorn.rb.example but with your settings.
 * Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.1/config.yml.example but with your settings.
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your settings.
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your settings.
 * Copy rack attack middleware config
 
 ```bash
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index d2125e013bc344daecb20541b16dbc577ca16cb4..d7fa370fe2a81b52d4677c9995dbc67a8e75ac2e 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -83,3 +83,22 @@ Feature: Profile
     Given I visit profile design page
     When I change my code preview theme
     Then I should receive feedback that the changes were saved
+
+  @javascript
+  Scenario: I see the password strength indicator
+    Given I visit profile password page
+    When I try to set a weak password
+    Then I should see the input field yellow
+
+  @javascript
+  Scenario: I see the password strength indicator error
+    Given I visit profile password page
+    When I try to set a short password
+    Then I should see the input field red
+    And I should see the password error message
+
+  @javascript
+  Scenario: I see the password strength indicator with success
+    Given I visit profile password page
+    When I try to set a strong password
+    Then I should see the input field green
\ No newline at end of file
diff --git a/features/project/service.feature b/features/project/service.feature
index af88eaefa8f615ef52f27b163bae7c940adc0214..88fd038d45f6f6ba78df9422498e48cb979c047a 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -54,3 +54,9 @@ Feature: Project Services
     And I click email on push service link
     And I fill email on push settings
     Then I should see email on push service settings saved
+
+  Scenario: Activate Atlassian Bamboo CI service
+    When I visit project "Shop" services page
+    And I click Atlassian Bamboo CI service link
+    And I fill Atlassian Bamboo CI settings
+    Then I should see Atlassian Bamboo CI service settings saved
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index 6b5f88e5895b4d2a20bffcb52aca2a0ba178baa0..2a5850d091b05fe7d9c8ca1033f3c3d8164ae4d9 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -10,6 +10,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
 
   step 'I should see issues authored by me' do
     should_see(authored_issue)
+    should_see(authored_issue_on_public_project)
     should_not_see(assigned_issue)
     should_not_see(other_issue)
   end
@@ -22,6 +23,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
 
   step 'I have authored issues' do
     authored_issue
+    authored_issue_on_public_project
   end
 
   step 'I have assigned issues' do
@@ -64,6 +66,10 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
     @other_issue ||= create :issue, project: project
   end
 
+  def authored_issue_on_public_project
+    @authored_issue_on_public_project ||= create :issue, author: current_user, project: public_project
+  end
+
   def project
     @project ||= begin
                    project =create :project
@@ -71,4 +77,8 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
                    project
                  end
   end
+
+  def public_project
+    @public_project ||= create :project, :public
+  end
 end
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index 95c378fa20128903b65a432a347472ac8f5afbed..75e53173d3f2cdad71ec00b7a560e20b369f9d5e 100644
--- a/features/steps/dashboard/merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
@@ -4,13 +4,17 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
 
   step 'I should see merge requests assigned to me' do
     should_see(assigned_merge_request)
+    should_see(assigned_merge_request_from_fork)
     should_not_see(authored_merge_request)
+    should_not_see(authored_merge_request_from_fork)
     should_not_see(other_merge_request)
   end
 
   step 'I should see merge requests authored by me' do
     should_see(authored_merge_request)
+    should_see(authored_merge_request_from_fork)
     should_not_see(assigned_merge_request)
+    should_not_see(assigned_merge_request_from_fork)
     should_not_see(other_merge_request)
   end
 
@@ -22,10 +26,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
 
   step 'I have authored merge requests' do
     authored_merge_request
+    authored_merge_request_from_fork
   end
 
   step 'I have assigned merge requests' do
     assigned_merge_request
+    assigned_merge_request_from_fork
   end
 
   step 'I have other merge requests' do
@@ -53,15 +59,41 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
   end
 
   def assigned_merge_request
-    @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project
+    @assigned_merge_request ||= create :merge_request,
+                                  assignee: current_user,
+                                  target_project: project,
+                                  source_project: project
   end
 
   def authored_merge_request
-    @authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project
+    @authored_merge_request ||= create :merge_request,
+                                  source_branch: 'simple_merge_request',
+                                  author: current_user,
+                                  target_project: project,
+                                  source_project: project
   end
 
   def other_merge_request
-    @other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project
+    @other_merge_request ||= create :merge_request,
+                              source_branch: '2_3_notes_fix',
+                              target_project: project,
+                              source_project: project
+  end
+
+  def authored_merge_request_from_fork
+    @authored_merge_request_from_fork ||= create :merge_request,
+                                            source_branch: 'basic_page',
+                                            author: current_user,
+                                            target_project: public_project,
+                                            source_project: forked_project
+  end
+
+  def assigned_merge_request_from_fork
+    @assigned_merge_request_from_fork ||= create :merge_request,
+                                            source_branch: 'basic_page_fix',
+                                            assignee: current_user,
+                                            target_project: public_project,
+                                            source_project: forked_project
   end
 
   def project
@@ -71,4 +103,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
                    project
                  end
   end
+
+  def public_project
+    @public_project ||= create :project, :public
+  end
+
+  def forked_project
+    @forked_project ||= Projects::ForkService.new(public_project, current_user).execute
+  end
 end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index adfaefb164499da47c15debe2dac271528f8d064..6d747b65bae29bac72c8a758dde317210d7fe02b 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -58,16 +58,34 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
 
   step 'I try change my password w/o old one' do
     within '.update-password' do
-      fill_in "user_password", with: "22233344"
+      fill_in "user_password_profile", with: "22233344"
       fill_in "user_password_confirmation", with: "22233344"
       click_button "Save"
     end
   end
 
+  step 'I try to set a weak password' do
+    within '.update-password' do
+      fill_in "user_password_profile", with: "22233344"
+    end
+  end
+
+  step 'I try to set a short password' do
+    within '.update-password' do
+      fill_in "user_password_profile", with: "short"
+    end
+  end
+
+  step 'I try to set a strong password' do
+    within '.update-password' do
+      fill_in "user_password_profile", with: "Itulvo9z8uud%$"
+    end
+  end
+
   step 'I change my password' do
     within '.update-password' do
       fill_in "user_current_password", with: "12345678"
-      fill_in "user_password", with: "22233344"
+      fill_in "user_password_profile", with: "22233344"
       fill_in "user_password_confirmation", with: "22233344"
       click_button "Save"
     end
@@ -76,7 +94,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
   step 'I unsuccessfully change my password' do
     within '.update-password' do
       fill_in "user_current_password", with: "12345678"
-      fill_in "user_password", with: "password"
+      fill_in "user_password_profile", with: "password"
       fill_in "user_password_confirmation", with: "confirmation"
       click_button "Save"
     end
@@ -86,6 +104,22 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
     page.should have_content "You must provide a valid current password"
   end
 
+  step 'I should see the input field yellow' do
+    page.should have_css 'div.has-warning'
+  end
+
+  step 'I should see the input field green' do
+    page.should have_css 'div.has-success'
+  end
+
+  step 'I should see the input field red' do
+    page.should have_css 'div.has-error'
+  end
+
+  step 'I should see the password error message' do
+    page.should have_content 'Your password is too short'
+  end
+
   step "I should see a password error message" do
     page.should have_content "Password confirmation doesn't match"
   end
@@ -146,7 +180,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
 
   step 'I submit new password' do
     fill_in :user_current_password, with: '12345678'
-    fill_in :user_password, with: '12345678'
+    fill_in :user_password_profile, with: '12345678'
     fill_in :user_password_confirmation, with: '12345678'
     click_button "Set new password"
   end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index f7fff8e64f9d427842514386a8455b30c521e59a..5e7312d90ffca01706671b75cdf5b7a5bea6c8a5 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -4,7 +4,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
   include SharedPaths
 
   step 'change project settings' do
-    fill_in 'project_name', with: 'NewName'
+    fill_in 'project_name_edit', with: 'NewName'
     uncheck 'project_issues_enabled'
   end
 
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 5bd60f99c84ee02e52f41c532b22b6f3ac586458..17d62210d10ccf9a147ca8f78d5d628afd1e40a4 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -14,6 +14,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
     page.should have_content 'GitLab CI'
     page.should have_content 'Assembla'
     page.should have_content 'Pushover'
+    page.should have_content 'Atlassian Bamboo'
   end
 
   step 'I click gitlab-ci service link' do
@@ -137,4 +138,23 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
     find_field('Priority').find('option[selected]').value.should == '1'
     find_field('Sound').find('option[selected]').value.should == 'bike'
   end
+
+  step 'I click Atlassian Bamboo CI service link' do
+    click_link 'Atlassian Bamboo CI'
+  end
+
+  step 'I fill Atlassian Bamboo CI settings' do
+    check 'Active'
+    fill_in 'Bamboo url', with: 'http://bamboo.example.com'
+    fill_in 'Build key', with: 'KEY'
+    fill_in 'Username', with: 'user'
+    fill_in 'Password', with: 'verySecret'
+    click_button 'Save'
+  end
+
+  step 'I should see Atlassian Bamboo CI service settings saved' do
+    find_field('Bamboo url').value.should == 'http://bamboo.example.com'
+    find_field('Build key').value.should == 'KEY'
+    find_field('Username').value.should == 'user'
+  end
 end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 2c7cd9038c3504d33abe0e9a5104a45e5a5b73f7..d26667ba3f7ad34db41fbf8c1df19cc04b7cacfe 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -27,6 +27,7 @@ module API
     helpers APIHelpers
 
     mount Groups
+    mount GroupMembers
     mount Users
     mount Projects
     mount Repositories
diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d596517c816e5cbc46cab7d08a34e4e763618956
--- /dev/null
+++ b/lib/api/group_members.rb
@@ -0,0 +1,80 @@
+module API
+  class GroupMembers < Grape::API
+    before { authenticate! }
+
+    resource :groups do
+      helpers do
+        def find_group(id)
+          group = Group.find(id)
+
+          if can?(current_user, :read_group, group)
+            group
+          else
+            render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403)
+          end
+        end
+
+        def validate_access_level?(level)
+          Gitlab::Access.options_with_owner.values.include? level.to_i
+        end
+      end
+
+      # Get a list of group members viewable by the authenticated user.
+      #
+      # Example Request:
+      #  GET /groups/:id/members
+      get ":id/members" do
+        group = find_group(params[:id])
+        members = group.group_members
+        users = (paginate members).collect(&:user)
+        present users, with: Entities::GroupMember, group: group
+      end
+
+      # Add a user to the list of group members
+      #
+      # Parameters:
+      #   id (required) - group id
+      #   user_id (required) - the users id
+      #   access_level (required) - Project access level
+      # Example Request:
+      #  POST /groups/:id/members
+      post ":id/members" do
+        group = find_group(params[:id])
+        authorize! :manage_group, group
+        required_attributes! [:user_id, :access_level]
+
+        unless validate_access_level?(params[:access_level])
+          render_api_error!("Wrong access level", 422)
+        end
+
+        if group.group_members.find_by(user_id: params[:user_id])
+          render_api_error!("Already exists", 409)
+        end
+
+        group.add_users([params[:user_id]], params[:access_level])
+        member = group.group_members.find_by(user_id: params[:user_id])
+        present member.user, with: Entities::GroupMember, group: group
+      end
+
+      # Remove member.
+      #
+      # Parameters:
+      #   id (required) - group id
+      #   user_id (required) - the users id
+      #
+      # Example Request:
+      #   DELETE /groups/:id/members/:user_id
+      delete ":id/members/:user_id" do
+        group = find_group(params[:id])
+        authorize! :manage_group, group
+        member = group.group_members.find_by(user_id: params[:user_id])
+
+        if member.nil?
+          render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
+        else
+          member.destroy
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 4841e04689d481a81c476a19ae43dc5393c85de6..f0ab6938b1c3be04bc2413d4a474534ba5e23835 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -97,57 +97,6 @@ module API
           not_found!
         end
       end
-
-      # Get a list of group members viewable by the authenticated user.
-      #
-      # Example Request:
-      #  GET /groups/:id/members
-      get ":id/members" do
-        group = find_group(params[:id])
-        members = group.group_members
-        users = (paginate members).collect(&:user)
-        present users, with: Entities::GroupMember, group: group
-      end
-
-      # Add a user to the list of group members
-      #
-      # Parameters:
-      #   id (required) - group id
-      #   user_id (required) - the users id
-      #   access_level (required) - Project access level
-      # Example Request:
-      #  POST /groups/:id/members
-      post ":id/members" do
-        required_attributes! [:user_id, :access_level]
-        unless validate_access_level?(params[:access_level])
-          render_api_error!("Wrong access level", 422)
-        end
-        group = find_group(params[:id])
-        if group.group_members.find_by(user_id: params[:user_id])
-          render_api_error!("Already exists", 409)
-        end
-        group.add_users([params[:user_id]], params[:access_level])
-        member = group.group_members.find_by(user_id: params[:user_id])
-        present member.user, with: Entities::GroupMember, group: group
-      end
-
-      # Remove member.
-      #
-      # Parameters:
-      #   id (required) - group id
-      #   user_id (required) - the users id
-      #
-      # Example Request:
-      #   DELETE /groups/:id/members/:user_id
-      delete ":id/members/:user_id" do
-        group = find_group(params[:id])
-        member =  group.group_members.find_by(user_id: params[:user_id])
-        if member.nil?
-          render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
-        else
-          member.destroy
-        end
-      end
     end
   end
 end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 7f7d2f8e9a8837d9085d79750055a2712bd61d2f..7fcf97d1ad6531c9465bcb18670a64c403151046 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -178,7 +178,7 @@ module API
       #   DELETE /projects/:id
       delete ":id" do
         authorize! :remove_project, user_project
-        user_project.destroy
+        ::Projects::DestroyService.new(user_project, current_user, {}).execute
       end
 
       # Mark this project as forked from another
diff --git a/lib/disable_email_interceptor.rb b/lib/disable_email_interceptor.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1b80be112a436dfad99e68a4b6a8a1ab6d5373ba
--- /dev/null
+++ b/lib/disable_email_interceptor.rb
@@ -0,0 +1,8 @@
+# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
+class DisableEmailInterceptor
+
+  def self.delivering_email(message)
+    message.perform_deliveries = false
+    Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}"
+  end
+end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 98ba5a47ee53c8108830df92533ee96869952fb8..d291621935b718f2593a42fa2ff25b9b18c6b36d 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,6 +1,6 @@
 require 'spec_helper'
 
-describe "Projects", feature: true  do
+describe "Projects", feature: true, js: true do
   before { login_as :user }
 
   describe "DELETE /projects/:id" do
@@ -10,12 +10,23 @@ describe "Projects", feature: true  do
       visit edit_project_path(@project)
     end
 
-    it "should be correct path", js: true do
-      expect {
-        click_link "Remove project"
-        fill_in 'confirm_name_input', with: @project.path
-        click_button 'Confirm'
-      }.to change {Project.count}.by(-1)
+    it "should remove project" do
+      expect { remove_project }.to change {Project.count}.by(-1)
     end
+
+    it 'should delete the project from disk' do
+      expect(GitlabShellWorker).to(
+        receive(:perform_async).with(:remove_repository,
+                                     /#{@project.path_with_namespace}/)
+      ).twice
+
+      remove_project
+    end
+  end
+
+  def remove_project
+    click_link "Remove project"
+    fill_in 'confirm_name_input', with: @project.path
+    click_button 'Confirm'
   end
 end
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index 7b831c48611f67a675796eba5670a1777070198c..a1206989d39fbaf97e6e6c42eae0f32a3beb7cf4 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -11,7 +11,7 @@ describe 'Users', feature: true do
       fill_in "user_name", with: "Name Surname"
       fill_in "user_username", with: "Great"
       fill_in "user_email", with: "name@mail.com"
-      fill_in "user_password", with: "password1234"
+      fill_in "user_password_sign_up", with: "password1234"
       fill_in "user_password_confirmation", with: "password1234"
       expect { click_button "Sign up" }.to change {User.count}.by(1)
     end
diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8bf6ee2ed50851d2c37746442366da96d0f539e6
--- /dev/null
+++ b/spec/lib/disable_email_interceptor_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe DisableEmailInterceptor do
+  before do
+    ActionMailer::Base.register_interceptor(DisableEmailInterceptor)
+  end
+
+  it 'should not send emails' do
+    Gitlab.config.gitlab.stub(:email_enabled).and_return(false)
+    expect {
+      deliver_mail
+    }.not_to change(ActionMailer::Base.deliveries, :count)
+  end
+
+  after do
+    # Removing interceptor from the list because unregister_interceptor is
+    # implemented in later version of mail gem
+    # See: https://github.com/mikel/mail/pull/705
+    Mail.class_variable_set(:@@delivery_interceptors, [])
+  end
+
+  def deliver_mail
+    key = create :personal_key
+    Notify.new_ssh_key_email(key.id)
+  end
+end
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ca6f11b2a4dcb9295b33a282e0eccf614d01635f
--- /dev/null
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe Issue, "Mentionable" do
+  describe :mentioned_users do
+    let!(:user) { create(:user, username: 'stranger') }
+    let!(:user2) { create(:user, username: 'john') }
+    let!(:issue) { create(:issue, description: '@stranger mentioned') }
+
+    subject { issue.mentioned_users }
+
+    it { should include(user) }
+    it { should_not include(user2) }
+  end
+end
diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4957186f6056d0b4e5269082a8ac537debbeb06c
--- /dev/null
+++ b/spec/requests/api/group_members_spec.rb
@@ -0,0 +1,136 @@
+require 'spec_helper'
+
+describe API::API, api: true  do
+  include ApiHelpers
+
+  let(:owner) { create(:user) }
+  let(:reporter) { create(:user) }
+  let(:developer) { create(:user) }
+  let(:master) { create(:user) }
+  let(:guest) { create(:user) }
+  let(:stranger) { create(:user) }
+
+  let!(:group_with_members) do
+    group = create(:group)
+    group.add_users([reporter.id], GroupMember::REPORTER)
+    group.add_users([developer.id], GroupMember::DEVELOPER)
+    group.add_users([master.id], GroupMember::MASTER)
+    group.add_users([guest.id], GroupMember::GUEST)
+    group
+  end
+
+  let!(:group_no_members) { create(:group) }
+
+  before do
+    group_with_members.add_owner owner
+    group_no_members.add_owner owner
+  end
+
+  describe "GET /groups/:id/members" do
+    context "when authenticated as user that is part or the group" do
+      it "each user: should return an array of members groups of group3" do
+        [owner, master, developer, reporter, guest].each do |user|
+          get api("/groups/#{group_with_members.id}/members", user)
+          response.status.should == 200
+          json_response.should be_an Array
+          json_response.size.should == 5
+          json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER
+          json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER
+          json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER
+          json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER
+          json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST
+        end
+      end
+
+      it "users not part of the group should get access error" do
+        get api("/groups/#{group_with_members.id}/members", stranger)
+        response.status.should == 403
+      end
+    end
+  end
+
+  describe "POST /groups/:id/members" do
+    context "when not a member of the group" do
+      it "should not add guest as member of group_no_members when adding being done by person outside the group" do
+        post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER
+        response.status.should == 403
+      end
+    end
+
+    context "when a member of the group" do
+      it "should return ok and add new member" do
+        new_user = create(:user)
+
+        expect {
+          post api("/groups/#{group_no_members.id}/members", owner),
+          user_id: new_user.id, access_level: GroupMember::MASTER
+        }.to change { group_no_members.members.count }.by(1)
+
+        response.status.should == 201
+        json_response['name'].should == new_user.name
+        json_response['access_level'].should == GroupMember::MASTER
+      end
+
+      it "should not allow guest to modify group members" do
+        new_user = create(:user)
+
+        expect {
+          post api("/groups/#{group_with_members.id}/members", guest),
+          user_id: new_user.id, access_level: GroupMember::MASTER
+        }.not_to change { group_with_members.members.count }
+
+        response.status.should == 403
+      end
+
+      it "should return error if member already exists" do
+        post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER
+        response.status.should == 409
+      end
+
+      it "should return a 400 error when user id is not given" do
+        post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER
+        response.status.should == 400
+      end
+
+      it "should return a 400 error when access level is not given" do
+        post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id
+        response.status.should == 400
+      end
+
+      it "should return a 422 error when access level is not known" do
+        post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234
+        response.status.should == 422
+      end
+    end
+  end
+
+  describe "DELETE /groups/:id/members/:user_id" do
+    context "when not a member of the group" do
+      it "should not delete guest's membership of group_with_members" do
+        random_user = create(:user)
+        delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user)
+        response.status.should == 403
+      end
+    end
+
+    context "when a member of the group" do
+      it "should delete guest's membership of group" do
+        expect {
+          delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
+        }.to change { group_with_members.members.count }.by(-1)
+
+        response.status.should == 200
+      end
+
+      it "should return a 404 error when user id is not known" do
+        delete api("/groups/#{group_with_members.id}/members/1328", owner)
+        response.status.should == 404
+      end
+
+      it "should not allow guest to modify group members" do
+        delete api("/groups/#{group_with_members.id}/members/#{master.id}", guest)
+        response.status.should == 403
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 42ccad71aafdf586a2f5968d7d0ddfcc8b090e1a..8dfd2cd650e79965f2ebf24c388aada08553d8da 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -165,114 +165,4 @@ describe API::API, api: true  do
       end
     end
   end
-
-  describe "members" do
-    let(:owner) { create(:user) }
-    let(:reporter) { create(:user) }
-    let(:developer) { create(:user) }
-    let(:master) { create(:user) }
-    let(:guest) { create(:user) }
-    let!(:group_with_members) do
-      group = create(:group)
-      group.add_users([reporter.id], GroupMember::REPORTER)
-      group.add_users([developer.id], GroupMember::DEVELOPER)
-      group.add_users([master.id], GroupMember::MASTER)
-      group.add_users([guest.id], GroupMember::GUEST)
-      group
-    end
-    let!(:group_no_members) { create(:group) }
-
-    before do
-      group_with_members.add_owner owner
-      group_no_members.add_owner owner
-    end
-
-    describe "GET /groups/:id/members" do
-      context "when authenticated as user that is part or the group" do
-        it "each user: should return an array of members groups of group3" do
-          [owner, master, developer, reporter, guest].each do |user|
-            get api("/groups/#{group_with_members.id}/members", user)
-            response.status.should == 200
-            json_response.should be_an Array
-            json_response.size.should == 5
-            json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER
-            json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER
-            json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER
-            json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER
-            json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST
-          end
-        end
-
-        it "users not part of the group should get access error" do
-          get api("/groups/#{group_with_members.id}/members", user1)
-          response.status.should == 403
-        end
-      end
-    end
-
-    describe "POST /groups/:id/members" do
-      context "when not a member of the group" do
-        it "should not add guest as member of group_no_members when adding being done by person outside the group" do
-          post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER
-          response.status.should == 403
-        end
-      end
-
-      context "when a member of the group" do
-        it "should return ok and add new member" do
-          count_before=group_no_members.group_members.count
-          new_user = create(:user)
-          post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: GroupMember::MASTER
-          response.status.should == 201
-          json_response['name'].should == new_user.name
-          json_response['access_level'].should == GroupMember::MASTER
-          group_no_members.group_members.count.should == count_before + 1
-        end
-
-        it "should return error if member already exists" do
-          post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER
-          response.status.should == 409
-        end
-
-        it "should return a 400 error when user id is not given" do
-          post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER
-          response.status.should == 400
-        end
-
-        it "should return a 400 error when access level is not given" do
-          post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id
-          response.status.should == 400
-        end
-
-        it "should return a 422 error when access level is not known" do
-          post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234
-          response.status.should == 422
-        end
-      end
-    end
-
-    describe "DELETE /groups/:id/members/:user_id" do
-      context "when not a member of the group" do
-        it "should not delete guest's membership of group_with_members" do
-          random_user = create(:user)
-          delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user)
-          response.status.should == 403
-        end
-      end
-
-      context "when a member of the group" do
-        it "should delete guest's membership of group" do
-          count_before=group_with_members.group_members.count
-          delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
-          response.status.should == 200
-          group_with_members.group_members.count.should == count_before - 1
-        end
-
-        it "should return a 404 error when user id is not known" do
-          delete api("/groups/#{group_with_members.id}/members/1328", owner)
-          response.status.should == 404
-        end
-      end
-    end
-  end
 end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index aa1437c71aa090b8f27478ee23278b1682c54ac9..ba7ec7b2be909381f95379d0c220a065a98ea05d 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -632,6 +632,11 @@ describe API::API, api: true  do
   describe "DELETE /projects/:id" do
     context "when authenticated as user" do
       it "should remove project" do
+        expect(GitlabShellWorker).to(
+          receive(:perform_async).with(:remove_repository,
+                                       /#{project.path_with_namespace}/)
+        ).twice
+
         delete api("/projects/#{project.id}", user)
         response.status.should == 200
       end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 2508dfc45654622ddfa19f37abf6f002ff7d7159..79d0526ff89cb3b1d2a8793a3b456f304b2a426e 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -3,15 +3,12 @@ require 'spec_helper'
 describe Projects::TransferService do
   let(:user) { create(:user) }
   let(:group) { create(:group) }
-  let(:group2) { create(:group) }
   let(:project) { create(:project, namespace: user.namespace) }
 
   context 'namespace -> namespace' do
     before do
       group.add_owner(user)
-      @service = Projects::TransferService.new(project, user, namespace_id: group.id)
-      @service.gitlab_shell.stub(mv_repository: true)
-      @result = @service.execute
+      @result = transfer_project(project, user, namespace_id: group.id)
     end
 
     it { @result.should be_true }
@@ -20,24 +17,25 @@ describe Projects::TransferService do
 
   context 'namespace -> no namespace' do
     before do
-      group.add_owner(user)
-      @service = Projects::TransferService.new(project, user, namespace_id: nil)
-      @service.gitlab_shell.stub(mv_repository: true)
-      @result = @service.execute
+      @result = transfer_project(project, user, namespace_id: nil)
     end
 
+    it { @result.should_not be_nil } # { result.should be_false } passes on nil
     it { @result.should be_false }
     it { project.namespace.should == user.namespace }
   end
 
   context 'namespace -> not allowed namespace' do
     before do
-      @service = Projects::TransferService.new(project, user, namespace_id: group2.id)
-      @service.gitlab_shell.stub(mv_repository: true)
-      @result = @service.execute
+      @result = transfer_project(project, user, namespace_id: group.id)
     end
 
+    it { @result.should_not be_nil } # { result.should be_false } passes on nil
     it { @result.should be_false }
     it { project.namespace.should == user.namespace }
   end
+
+  def transfer_project(project, user, params)
+    Projects::TransferService.new(project, user, params).execute
+  end
 end
diff --git a/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js b/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js
new file mode 100644
index 0000000000000000000000000000000000000000..ee374a07fab69a5e925e79c69029bf9d0f9c30c6
--- /dev/null
+++ b/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js
@@ -0,0 +1,659 @@
+/*!
+ * jQuery Password Strength plugin for Twitter Bootstrap
+ *
+ * Copyright (c) 2008-2013 Tane Piper
+ * Copyright (c) 2013 Alejandro Blanco
+ * Dual licensed under the MIT and GPL licenses.
+ */
+
+(function (jQuery) {
+// Source: src/rules.js
+
+  var rulesEngine = {};
+
+  try {
+    if (!jQuery && module && module.exports) {
+      var jQuery = require("jquery"),
+        jsdom = require("jsdom").jsdom;
+      jQuery = jQuery(jsdom().parentWindow);
+    }
+  } catch (ignore) {}
+
+  (function ($, rulesEngine) {
+    "use strict";
+    var validation = {};
+
+    rulesEngine.forbiddenSequences = [
+      "0123456789", "abcdefghijklmnopqrstuvwxyz", "qwertyuiop", "asdfghjkl",
+      "zxcvbnm", "!@#$%^&*()_+"
+    ];
+
+    validation.wordNotEmail = function (options, word, score) {
+      if (word.match(/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i)) {
+        return score;
+      }
+      return 0;
+    };
+
+    validation.wordLength = function (options, word, score) {
+      var wordlen = word.length,
+        lenScore = Math.pow(wordlen, options.rules.raisePower);
+      if (wordlen < options.common.minChar) {
+        lenScore = (lenScore + score);
+      }
+      return lenScore;
+    };
+
+    validation.wordSimilarToUsername = function (options, word, score) {
+      var username = $(options.common.usernameField).val();
+      if (username && word.toLowerCase().match(username.toLowerCase())) {
+        return score;
+      }
+      return 0;
+    };
+
+    validation.wordTwoCharacterClasses = function (options, word, score) {
+      if (word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) ||
+        (word.match(/([a-zA-Z])/) && word.match(/([0-9])/)) ||
+        (word.match(/(.[!,@,#,$,%,\^,&,*,?,_,~])/) && word.match(/[a-zA-Z0-9_]/))) {
+        return score;
+      }
+      return 0;
+    };
+
+    validation.wordRepetitions = function (options, word, score) {
+      if (word.match(/(.)\1\1/)) { return score; }
+      return 0;
+    };
+
+    validation.wordSequences = function (options, word, score) {
+      var found = false,
+        j;
+      if (word.length > 2) {
+        $.each(rulesEngine.forbiddenSequences, function (idx, seq) {
+          var sequences = [seq, seq.split('').reverse().join('')];
+          $.each(sequences, function (idx, sequence) {
+            for (j = 0; j < (word.length - 2); j += 1) { // iterate the word trough a sliding window of size 3:
+              if (sequence.indexOf(word.toLowerCase().substring(j, j + 3)) > -1) {
+                found = true;
+              }
+            }
+          });
+        });
+        if (found) { return score; }
+      }
+      return 0;
+    };
+
+    validation.wordLowercase = function (options, word, score) {
+      return word.match(/[a-z]/) && score;
+    };
+
+    validation.wordUppercase = function (options, word, score) {
+      return word.match(/[A-Z]/) && score;
+    };
+
+    validation.wordOneNumber = function (options, word, score) {
+      return word.match(/\d+/) && score;
+    };
+
+    validation.wordThreeNumbers = function (options, word, score) {
+      return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score;
+    };
+
+    validation.wordOneSpecialChar = function (options, word, score) {
+      return word.match(/.[!,@,#,$,%,\^,&,*,?,_,~]/) && score;
+    };
+
+    validation.wordTwoSpecialChar = function (options, word, score) {
+      return word.match(/(.*[!,@,#,$,%,\^,&,*,?,_,~].*[!,@,#,$,%,\^,&,*,?,_,~])/) && score;
+    };
+
+    validation.wordUpperLowerCombo = function (options, word, score) {
+      return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score;
+    };
+
+    validation.wordLetterNumberCombo = function (options, word, score) {
+      return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score;
+    };
+
+    validation.wordLetterNumberCharCombo = function (options, word, score) {
+      return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score;
+    };
+
+    rulesEngine.validation = validation;
+
+    rulesEngine.executeRules = function (options, word) {
+      var totalScore = 0;
+
+      $.each(options.rules.activated, function (rule, active) {
+        if (active) {
+          var score = options.rules.scores[rule],
+            funct = rulesEngine.validation[rule],
+            result,
+            errorMessage;
+
+          if (!$.isFunction(funct)) {
+            funct = options.rules.extra[rule];
+          }
+
+          if ($.isFunction(funct)) {
+            result = funct(options, word, score);
+            if (result) {
+              totalScore += result;
+            }
+            if (result < 0 || (!$.isNumeric(result) && !result)) {
+              errorMessage = options.ui.spanError(options, rule);
+              if (errorMessage.length > 0) {
+                options.instances.errors.push(errorMessage);
+              }
+            }
+          }
+        }
+      });
+
+      return totalScore;
+    };
+  }(jQuery, rulesEngine));
+
+  try {
+    if (module && module.exports) {
+      module.exports = rulesEngine;
+    }
+  } catch (ignore) {}
+
+// Source: src/options.js
+
+
+
+
+  var defaultOptions = {};
+
+  defaultOptions.common = {};
+  defaultOptions.common.minChar = 6;
+  defaultOptions.common.usernameField = "#username";
+  defaultOptions.common.userInputs = [
+    // Selectors for input fields with user input
+  ];
+  defaultOptions.common.onLoad = undefined;
+  defaultOptions.common.onKeyUp = undefined;
+  defaultOptions.common.zxcvbn = false;
+  defaultOptions.common.debug = false;
+
+  defaultOptions.rules = {};
+  defaultOptions.rules.extra = {};
+  defaultOptions.rules.scores = {
+    wordNotEmail: -100,
+    wordLength: -50,
+    wordSimilarToUsername: -100,
+    wordSequences: -50,
+    wordTwoCharacterClasses: 2,
+    wordRepetitions: -25,
+    wordLowercase: 1,
+    wordUppercase: 3,
+    wordOneNumber: 3,
+    wordThreeNumbers: 5,
+    wordOneSpecialChar: 3,
+    wordTwoSpecialChar: 5,
+    wordUpperLowerCombo: 2,
+    wordLetterNumberCombo: 2,
+    wordLetterNumberCharCombo: 2
+  };
+  defaultOptions.rules.activated = {
+    wordNotEmail: true,
+    wordLength: true,
+    wordSimilarToUsername: true,
+    wordSequences: true,
+    wordTwoCharacterClasses: false,
+    wordRepetitions: false,
+    wordLowercase: true,
+    wordUppercase: true,
+    wordOneNumber: true,
+    wordThreeNumbers: true,
+    wordOneSpecialChar: true,
+    wordTwoSpecialChar: true,
+    wordUpperLowerCombo: true,
+    wordLetterNumberCombo: true,
+    wordLetterNumberCharCombo: true
+  };
+  defaultOptions.rules.raisePower = 1.4;
+
+  defaultOptions.ui = {};
+  defaultOptions.ui.bootstrap2 = false;
+  defaultOptions.ui.showProgressBar = true;
+  defaultOptions.ui.showPopover = false;
+  defaultOptions.ui.showStatus = false;
+  defaultOptions.ui.spanError = function (options, key) {
+    "use strict";
+    var text = options.ui.errorMessages[key];
+    if (!text) { return ''; }
+    return '<span style="color: #d52929">' + text + '</span>';
+  };
+  defaultOptions.ui.errorMessages = {
+    wordLength: "Your password is too short",
+    wordNotEmail: "Do not use your email as your password",
+    wordSimilarToUsername: "Your password cannot contain your username",
+    wordTwoCharacterClasses: "Use different character classes",
+    wordRepetitions: "Too many repetitions",
+    wordSequences: "Your password contains sequences"
+  };
+  defaultOptions.ui.verdicts = ["Weak", "Normal", "Medium", "Strong", "Very Strong"];
+  defaultOptions.ui.showVerdicts = true;
+  defaultOptions.ui.showVerdictsInsideProgressBar = false;
+  defaultOptions.ui.showErrors = false;
+  defaultOptions.ui.container = undefined;
+  defaultOptions.ui.viewports = {
+    progress: undefined,
+    verdict: undefined,
+    errors: undefined
+  };
+  defaultOptions.ui.scores = [14, 26, 38, 50];
+
+// Source: src/ui.js
+
+
+
+
+  var ui = {};
+
+  (function ($, ui) {
+    "use strict";
+
+    var barClasses = ["danger", "warning", "success"],
+      statusClasses = ["error", "warning", "success"];
+
+    ui.getContainer = function (options, $el) {
+      var $container;
+
+      $container = $(options.ui.container);
+      if (!($container && $container.length === 1)) {
+        $container = $el.parent();
+      }
+      return $container;
+    };
+
+    ui.findElement = function ($container, viewport, cssSelector) {
+      if (viewport) {
+        return $container.find(viewport).find(cssSelector);
+      }
+      return $container.find(cssSelector);
+    };
+
+    ui.getUIElements = function (options, $el) {
+      var $container, result;
+
+      if (options.instances.viewports) {
+        return options.instances.viewports;
+      }
+
+      $container = ui.getContainer(options, $el);
+
+      result = {};
+      result.$progressbar = ui.findElement($container, options.ui.viewports.progress, "div.progress");
+      if (options.ui.showVerdictsInsideProgressBar) {
+        result.$verdict = result.$progressbar.find("span.password-verdict");
+      }
+
+      if (!options.ui.showPopover) {
+        if (!options.ui.showVerdictsInsideProgressBar) {
+          result.$verdict = ui.findElement($container, options.ui.viewports.verdict, "span.password-verdict");
+        }
+        result.$errors = ui.findElement($container, options.ui.viewports.errors, "ul.error-list");
+      }
+
+      options.instances.viewports = result;
+      return result;
+    };
+
+    ui.initProgressBar = function (options, $el) {
+      var $container = ui.getContainer(options, $el),
+        progressbar = "<div class='progress'><div class='";
+
+      if (!options.ui.bootstrap2) {
+        progressbar += "progress-";
+      }
+      progressbar += "bar'>";
+      if (options.ui.showVerdictsInsideProgressBar) {
+        progressbar += "<span class='password-verdict'></span>";
+      }
+      progressbar += "</div></div>";
+
+      if (options.ui.viewports.progress) {
+        $container.find(options.ui.viewports.progress).append(progressbar);
+      } else {
+        $(progressbar).insertAfter($el);
+      }
+    };
+
+    ui.initHelper = function (options, $el, html, viewport) {
+      var $container = ui.getContainer(options, $el);
+      if (viewport) {
+        $container.find(viewport).append(html);
+      } else {
+        $(html).insertAfter($el);
+      }
+    };
+
+    ui.initVerdict = function (options, $el) {
+      ui.initHelper(options, $el, "<span class='password-verdict'></span>",
+        options.ui.viewports.verdict);
+    };
+
+    ui.initErrorList = function (options, $el) {
+      ui.initHelper(options, $el, "<ul class='error-list'></ul>",
+        options.ui.viewports.errors);
+    };
+
+    ui.initPopover = function (options, $el) {
+      $el.popover("destroy");
+      $el.popover({
+        html: true,
+        placement: "top",
+        trigger: "manual",
+        content: " "
+      });
+    };
+
+    ui.initUI = function (options, $el) {
+      if (options.ui.showPopover) {
+        ui.initPopover(options, $el);
+      } else {
+        if (options.ui.showErrors) { ui.initErrorList(options, $el); }
+        if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
+          ui.initVerdict(options, $el);
+        }
+      }
+      if (options.ui.showProgressBar) {
+        ui.initProgressBar(options, $el);
+      }
+    };
+
+    ui.possibleProgressBarClasses = ["danger", "warning", "success"];
+
+    ui.updateProgressBar = function (options, $el, cssClass, percentage) {
+      var $progressbar = ui.getUIElements(options, $el).$progressbar,
+        $bar = $progressbar.find(".progress-bar"),
+        cssPrefix = "progress-";
+
+      if (options.ui.bootstrap2) {
+        $bar = $progressbar.find(".bar");
+        cssPrefix = "";
+      }
+
+      $.each(ui.possibleProgressBarClasses, function (idx, value) {
+        $bar.removeClass(cssPrefix + "bar-" + value);
+      });
+      $bar.addClass(cssPrefix + "bar-" + barClasses[cssClass]);
+      $bar.css("width", percentage + '%');
+    };
+
+    ui.updateVerdict = function (options, $el, text) {
+      var $verdict = ui.getUIElements(options, $el).$verdict;
+      $verdict.text(text);
+    };
+
+    ui.updateErrors = function (options, $el) {
+      var $errors = ui.getUIElements(options, $el).$errors,
+        html = "";
+      $.each(options.instances.errors, function (idx, err) {
+        html += "<li>" + err + "</li>";
+      });
+      $errors.html(html);
+    };
+
+    ui.updatePopover = function (options, $el, verdictText) {
+      var popover = $el.data("bs.popover"),
+        html = "",
+        hide = true;
+
+      if (options.ui.showVerdicts &&
+        !options.ui.showVerdictsInsideProgressBar &&
+        verdictText.length > 0) {
+        html = "<h5><span class='password-verdict'>" + verdictText +
+          "</span></h5>";
+        hide = false;
+      }
+      if (options.ui.showErrors) {
+        html += "<div><ul class='error-list' style='margin-bottom: 0; margin-left: -20px'>";
+        $.each(options.instances.errors, function (idx, err) {
+          html += "<li>" + err + "</li>";
+          hide = false;
+        });
+        html += "</ul></div>";
+      }
+
+      if (hide) {
+        $el.popover("hide");
+        return;
+      }
+
+      if (options.ui.bootstrap2) { popover = $el.data("popover"); }
+
+      if (popover.$arrow && popover.$arrow.parents("body").length > 0) {
+        $el.find("+ .popover .popover-content").html(html);
+      } else {
+        // It's hidden
+        popover.options.content = html;
+        $el.popover("show");
+      }
+    };
+
+    ui.updateFieldStatus = function (options, $el, cssClass) {
+      var targetClass = options.ui.bootstrap2 ? ".control-group" : ".form-group",
+        $container = $el.parents(targetClass).first();
+
+      $.each(statusClasses, function (idx, css) {
+        if (!options.ui.bootstrap2) { css = "has-" + css; }
+        $container.removeClass(css);
+      });
+
+      cssClass = statusClasses[cssClass];
+      if (!options.ui.bootstrap2) { cssClass = "has-" + cssClass; }
+      $container.addClass(cssClass);
+    };
+
+    ui.percentage = function (score, maximun) {
+      var result = Math.floor(100 * score / maximun);
+      result = result < 0 ? 0 : result;
+      result = result > 100 ? 100 : result;
+      return result;
+    };
+
+    ui.getVerdictAndCssClass = function (options, score) {
+      var cssClass, verdictText, level;
+
+      if (score <= 0) {
+        cssClass = 0;
+        level = -1;
+        verdictText = options.ui.verdicts[0];
+      } else if (score < options.ui.scores[0]) {
+        cssClass = 0;
+        level = 0;
+        verdictText = options.ui.verdicts[0];
+      } else if (score < options.ui.scores[1]) {
+        cssClass = 0;
+        level = 1;
+        verdictText = options.ui.verdicts[1];
+      } else if (score < options.ui.scores[2]) {
+        cssClass = 1;
+        level = 2;
+        verdictText = options.ui.verdicts[2];
+      } else if (score < options.ui.scores[3]) {
+        cssClass = 1;
+        level = 3;
+        verdictText = options.ui.verdicts[3];
+      } else {
+        cssClass = 2;
+        level = 4;
+        verdictText = options.ui.verdicts[4];
+      }
+
+      return [verdictText, cssClass, level];
+    };
+
+    ui.updateUI = function (options, $el, score) {
+      var cssClass, barPercentage, verdictText;
+
+      cssClass = ui.getVerdictAndCssClass(options, score);
+      verdictText = cssClass[0];
+      cssClass = cssClass[1];
+
+      if (options.ui.showProgressBar) {
+        barPercentage = ui.percentage(score, options.ui.scores[3]);
+        ui.updateProgressBar(options, $el, cssClass, barPercentage);
+        if (options.ui.showVerdictsInsideProgressBar) {
+          ui.updateVerdict(options, $el, verdictText);
+        }
+      }
+
+      if (options.ui.showStatus) {
+        ui.updateFieldStatus(options, $el, cssClass);
+      }
+
+      if (options.ui.showPopover) {
+        ui.updatePopover(options, $el, verdictText);
+      } else {
+        if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
+          ui.updateVerdict(options, $el, verdictText);
+        }
+        if (options.ui.showErrors) {
+          ui.updateErrors(options, $el);
+        }
+      }
+    };
+  }(jQuery, ui));
+
+// Source: src/methods.js
+
+
+
+
+  var methods = {};
+
+  (function ($, methods) {
+    "use strict";
+    var onKeyUp, applyToAll;
+
+    onKeyUp = function (event) {
+      var $el = $(event.target),
+        options = $el.data("pwstrength-bootstrap"),
+        word = $el.val(),
+        userInputs,
+        verdictText,
+        verdictLevel,
+        score;
+
+      if (options === undefined) { return; }
+
+      options.instances.errors = [];
+      if (options.common.zxcvbn) {
+        userInputs = [];
+        $.each(options.common.userInputs, function (idx, selector) {
+          userInputs.push($(selector).val());
+        });
+        userInputs.push($(options.common.usernameField).val());
+        score = zxcvbn(word, userInputs).entropy;
+      } else {
+        score = rulesEngine.executeRules(options, word);
+      }
+      ui.updateUI(options, $el, score);
+      verdictText = ui.getVerdictAndCssClass(options, score);
+      verdictLevel = verdictText[2];
+      verdictText = verdictText[0];
+
+      if (options.common.debug) { console.log(score + ' - ' + verdictText); }
+
+      if ($.isFunction(options.common.onKeyUp)) {
+        options.common.onKeyUp(event, {
+          score: score,
+          verdictText: verdictText,
+          verdictLevel: verdictLevel
+        });
+      }
+    };
+
+    methods.init = function (settings) {
+      this.each(function (idx, el) {
+        // Make it deep extend (first param) so it extends too the
+        // rules and other inside objects
+        var clonedDefaults = $.extend(true, {}, defaultOptions),
+          localOptions = $.extend(true, clonedDefaults, settings),
+          $el = $(el);
+
+        localOptions.instances = {};
+        $el.data("pwstrength-bootstrap", localOptions);
+        $el.on("keyup", onKeyUp);
+        $el.on("change", onKeyUp);
+        $el.on("onpaste", onKeyUp);
+
+        ui.initUI(localOptions, $el);
+        if ($.trim($el.val())) { // Not empty, calculate the strength
+          $el.trigger("keyup");
+        }
+
+        if ($.isFunction(localOptions.common.onLoad)) {
+          localOptions.common.onLoad();
+        }
+      });
+
+      return this;
+    };
+
+    methods.destroy = function () {
+      this.each(function (idx, el) {
+        var $el = $(el),
+          options = $el.data("pwstrength-bootstrap"),
+          elements = ui.getUIElements(options, $el);
+        elements.$progressbar.remove();
+        elements.$verdict.remove();
+        elements.$errors.remove();
+        $el.removeData("pwstrength-bootstrap");
+      });
+    };
+
+    methods.forceUpdate = function () {
+      this.each(function (idx, el) {
+        var event = { target: el };
+        onKeyUp(event);
+      });
+    };
+
+    methods.addRule = function (name, method, score, active) {
+      this.each(function (idx, el) {
+        var options = $(el).data("pwstrength-bootstrap");
+
+        options.rules.activated[name] = active;
+        options.rules.scores[name] = score;
+        options.rules.extra[name] = method;
+      });
+    };
+
+    applyToAll = function (rule, prop, value) {
+      this.each(function (idx, el) {
+        $(el).data("pwstrength-bootstrap").rules[prop][rule] = value;
+      });
+    };
+
+    methods.changeScore = function (rule, score) {
+      applyToAll.call(this, rule, "scores", score);
+    };
+
+    methods.ruleActive = function (rule, active) {
+      applyToAll.call(this, rule, "activated", active);
+    };
+
+    $.fn.pwstrength = function (method) {
+      var result;
+
+      if (methods[method]) {
+        result = methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+      } else if (typeof method === "object" || !method) {
+        result = methods.init.apply(this, arguments);
+      } else {
+        $.error("Method " +  method + " does not exist on jQuery.pwstrength-bootstrap");
+      }
+
+      return result;
+    };
+  }(jQuery, methods));
+}(jQuery));
\ No newline at end of file