diff --git a/CHANGELOG b/CHANGELOG
index 74e340f888a66b8a54f6b6d280f2cdf69861cfaa..ccf5db321dd18976d50c97adad70b1c554f3d78b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -13,11 +13,20 @@ v 8.2.0 (unreleased)
   - Fix: Inability to reply to code comments in the MR view, if the MR comes from a fork
   - Use git follow flag for commits page when retrieve history for file or directory
   - Show merge request CI status on merge requests index page
+  - Extend yml syntax for only and except to support specifying repository path
   - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu)
   - Remove deprecated CI events from project settings page
   - Use issue editor as cross reference comment author when issue is edited with a new mention.
   - Improve personal snippet access workflow
   - [API] Add ability to fetch the commit ID of the last commit that actually touched a file
+  - Add "New file" link to dropdown on project page
+  - Include commit logs in project search
+  - Add "added", "modified" and "removed" properties to commit object in webhook
+  - Rename "Back to" links to "Go to" because its not always a case it point to place user come from
+
+v 8.1.3
+  - Spread out runner contacted_at updates
+  - New design for user profile page
 
 v 8.1.1
   - Fix cloning Wiki repositories via HTTP (Stan Hu)
@@ -34,6 +43,7 @@ v 8.1.0
   - Fix duplicate repositories in GitHub import page (Stan Hu)
   - Redirect to a default path if HTTP_REFERER is not set (Stan Hu)
   - Adds ability to create directories using the web editor (Ben Ford)
+  - Cleanup stuck CI builds
 
 v 8.1.0 (unreleased)
   - Send an email to admin email when a user is reported for spam (Jonathan Rochkind)
diff --git a/app/assets/javascripts/calendar.js.coffee b/app/assets/javascripts/calendar.js.coffee
index 4c4bc3d66edcd196f56c436bd2e89fa77290e2e4..2b1e20d3225f19dcc938d5aa9638be99b67169b4 100644
--- a/app/assets/javascripts/calendar.js.coffee
+++ b/app/assets/javascripts/calendar.js.coffee
@@ -25,7 +25,7 @@ class @Calendar
         30
       ]
       legendCellPadding: 3
-      cellSize: $('.user-calendar').width() / 80
+      cellSize: $('.user-calendar').width() / 76
       onClick: (date, count) ->
         formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()
         $.ajax
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 5949a0fd5adc0ac65509f2c675170f9b5f4acccf..8917c53b1f54be188a08869a83bb1aeec87150c6 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -100,7 +100,7 @@
   }
 
   .cover-desc {
-    padding: 0 $gl-padding;
+    padding: 0 $gl-padding 3px;
     color: $gl-text-color;
   }
 
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 04024419584e39ef6c732e6f92b5c667aeb00181..fe56266284b9d5c5709ce5d2cdd0f2d00eb6226e 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -180,3 +180,7 @@
     }
   }
 }
+
+.btn-clipboard {
+  border: none;
+}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index e1a1793be9c1564e787fd3c1c2bd0876d2cf6626..3d0b71e066e43a13476e1993b313e6244e76088f 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -387,6 +387,16 @@ table {
   }
 }
 
+.center-middle-menu {
+  @include nav-menu;
+  text-align: center;
+  margin: -$gl-padding;
+  height: auto;
+  margin-top: 0;
+  margin-bottom: 0;
+  border-bottom: 1px solid $border-color;
+}
+
 .dropzone .dz-preview .dz-progress {
   border-color: $border-color !important;
 }
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 4e121b95d13b14986749373eec8d66667eb3b998..e485487bcfd2c21dab4e68209f39955c28855429 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -33,6 +33,8 @@
 }
 
 li.commit {
+  list-style: none;
+
   .commit-row-title {
     font-size: $list-font-size;
     line-height: 20px;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index b7391e5303bff8f0bdbc5096c78fdd1e90af0125..bc1ad21305a8552d106037af14a46e2c34657233 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -53,3 +53,29 @@
   float: right;
   font-size: 12px;
 }
+
+.profile-link-holder {
+  display: inline;
+
+  &:after {
+    content: "\00B7";
+    padding: 0px 6px;
+    font-weight: bold;
+  }
+
+  &:last-child {
+    &:after {
+      content: "";
+      padding: 0;
+    }
+  }
+
+  a {
+    color: $blue-dark;
+    text-decoration: none;
+  }
+}
+
+.cal-heatmap-container {
+  margin: 0 auto;
+}
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 05c7d3de8bc25c9c02b612e2e02b9c7270fdd6b0..00d13a83ce8372b44a099389564baa534be8a981 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,7 +1,7 @@
 class ProjectsController < ApplicationController
   include ExtractsPath
 
-  prepend_before_filter :render_go_import, only: [:show]
+  prepend_before_action :render_go_import, only: [:show]
   skip_before_action :authenticate_user!, only: [:show, :activity]
   before_action :project, except: [:new, :create]
   before_action :repository, except: [:new, :create]
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index eb0408a95e58c3b64349ebdcf8d2633b0303082f..9bb42ec86b390b88a70d0494ed308f73e744118a 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -23,8 +23,8 @@ class SearchController < ApplicationController
 
     @search_results =
       if @project
-        unless %w(blobs notes issues merge_requests milestones wiki_blobs).
-          include?(@scope)
+        unless %w(blobs notes issues merge_requests milestones wiki_blobs
+                  commits).include?(@scope)
           @scope = 'blobs'
         end
 
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index 13437b2483f1f6a6f9fef45227419c196bd28c75..e58420d82d462268fc3ffa3a92659bc58766ecac 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -187,7 +187,7 @@ module Ci
     end
 
     def config_processor
-      @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file)
+      @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, gl_project.path_with_namespace)
     rescue Ci::GitlabCiYamlProcessor::ValidationError => e
       save_yaml_error(e.message)
       nil
diff --git a/app/models/repository.rb b/app/models/repository.rb
index c9b36bd81705e6a6a177174bace99e3701b01c2a..9266ba27f0a072a08ad32a3325d84b4a51fad764 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -87,6 +87,15 @@ class Repository
     commits
   end
 
+  def find_commits_by_message(query)
+    # Limited to 1000 commits for now, could be parameterized?
+    args = %W(git log --pretty=%H --max-count 1000 --grep=#{query})
+
+    git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
+    commits = git_log_results.map { |c| commit(c) }
+    commits
+  end
+
   def find_branch(name)
     branches.find { |branch| branch.name == name }
   end
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index ceb64ce3157f69fd054ece96b2fcfac9f66ea4fc..d1aa8f624637822930a008413c3843631f57fff3 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -11,6 +11,8 @@
         = hidden_field_tag :scope, 'merge_requests'
       - elsif current_controller?(:wikis)
         = hidden_field_tag :scope, 'wiki_blobs'
+      - elsif current_controller?(:commits)
+        = hidden_field_tag :scope, 'commits'
       - else
         = hidden_field_tag :search_code, true
 
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index eb35af22b93836d1af2fb7052c99f2875ac4f39e..319352876b4ec648b8b1315ad1669790cc979430 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,9 +1,9 @@
 %ul.nav.nav-sidebar
   = nav_link do
-    = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
+    = link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
       = icon('caret-square-o-left fw')
       %span
-        Back to dashboard
+        Go to dashboard
 
   %li.separate-item
 
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index 8075fe32fbce4b261630dafad8cc3b3b1d0cf372..c8411521f3692f40746882e3da47e8a8611e26e0 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,9 +1,9 @@
 %ul.nav.nav-sidebar
   = nav_link do
-    = link_to group_path(@group), title: 'Back to group', data: {placement: 'right'}, class: 'back-link' do
+    = link_to group_path(@group), title: 'Go to group', data: {placement: 'right'}, class: 'back-link' do
       = icon('caret-square-o-left fw')
       %span
-        Back to group
+        Go to group
 
   %li.separate-item
 
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 5a47b8e6db2ec0656e9526a5e474f09ef6246bce..0f3a793e30be18ac993bdc59ee800b60144ae48d 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,9 +1,9 @@
 %ul.nav.nav-sidebar
   = nav_link do
-    = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
+    = link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
       = icon('caret-square-o-left fw')
       %span
-        Back to dashboard
+        Go to dashboard
 
   %li.separate-item
 
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 53a913fe8f379e696555e05a8ef75d8febf0f8cc..20db2866d1f14cec86fa5b41385e0283c5207f5b 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,16 +1,16 @@
 %ul.nav.nav-sidebar
   - if @project.group
     = nav_link do
-      = link_to group_path(@project.group), title: 'Back to group', data: {placement: 'right'}, class: 'back-link' do
+      = link_to group_path(@project.group), title: 'Go to group', data: {placement: 'right'}, class: 'back-link' do
         = icon('caret-square-o-left fw')
         %span
-          Back to group
+          Go to group
   - else
     = nav_link do
-      = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
+      = link_to root_path, title: 'Go to dashboard', data: {placement: 'right'}, class: 'back-link' do
         = icon('caret-square-o-left fw')
         %span
-          Back to dashboard
+          Go to dashboard
 
   %li.separate-item
 
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 356ce09c3d74c1a29ab01b4018da9f11bf4140a8..a59939ccd31f5335652e8c161fdbca11057598af 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -1,9 +1,9 @@
 %ul.nav.nav-sidebar
   = nav_link do
-    = link_to project_path(@project), title: 'Back to project', data: {placement: 'right'}, class: 'back-link' do
+    = link_to project_path(@project), title: 'Go to project', data: {placement: 'right'}, class: 'back-link' do
       = icon('caret-square-o-left fw')
       %span
-        Back to project
+        Go to project
 
   %li.separate-item
 
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 4580c912692e79e4a0d34f762e3eb80a95785086..bed2b16249e62aa888a33739f9f6344766450eda 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -20,6 +20,10 @@
             New snippet
       - if can?(current_user, :push_code, @project)
         %li.divider
+        %li
+          = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'), title: 'New file' do
+            = icon('file fw')
+            New file
         %li
           = link_to new_namespace_project_branch_path(@project.namespace, @project) do
             = icon('code-fork fw')
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index d637abfa76b90588840b1e98c209e0c7521b7fd4..481451edb23ce0904bbb70b6e15fd2612faae374 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -42,6 +42,13 @@
           Wiki
           %span.badge
             = @search_results.wiki_blobs_count
+    %li{class: ("active" if @scope == 'commits')}
+      = link_to search_filter_path(scope: 'commits') do
+        = icon('history fw')
+        %span
+          Commits
+          %span.badge
+            = @search_results.commits_count
 
   - elsif @show_snippets
     %li{class: ("active" if @scope == 'snippet_blobs')}
diff --git a/app/views/search/results/_commit.html.haml b/app/views/search/results/_commit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..4e6c3965dc65dac0a96f427c8466b2091df7b37e
--- /dev/null
+++ b/app/views/search/results/_commit.html.haml
@@ -0,0 +1,2 @@
+.search-result-row
+  = render 'projects/commits/commit', project: @project, commit: commit
diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml
deleted file mode 100644
index a126a858ea85f0ce96e785dc2ebc0297a161d38f..0000000000000000000000000000000000000000
--- a/app/views/users/_projects.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present?
-  .panel.panel-default.contributed-projects
-    .panel-heading Projects contributed to
-    = render 'shared/projects/list',
-      projects: contributed_projects.sort_by(&:star_count).reverse,
-      projects_limit: 5, stars: true, avatar: false
-
-- if local_assigns.has_key?(:projects) && projects.present?
-  .panel.panel-default
-    .panel-heading Personal projects
-    = render 'shared/projects/list',
-      projects: projects.sort_by(&:star_count).reverse,
-      projects_limit: 10, stars: true, avatar: false
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 4ea4a1f92c2ddfb188f3c1a918549d1a60fd9eaa..e22d93aae84c79428c142d0d7dc6028ad8ee2d47 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -24,22 +24,27 @@
 
   .cover-desc
     - unless @user.public_email.blank?
-      = link_to @user.public_email, "mailto:#{@user.public_email}"
+      .profile-link-holder
+        = link_to @user.public_email, "mailto:#{@user.public_email}"
     - unless @user.skype.blank?
-      &middot;
-      = link_to "Skype", "skype:#{@user.skype}"
+      .profile-link-holder
+        = link_to "skype:#{@user.skype}", title: "Skype" do
+          = icon('skype')
     - unless @user.linkedin.blank?
-      &middot;
-      = link_to "LinkedIn", "http://www.linkedin.com/in/#{@user.linkedin}"
+      .profile-link-holder
+        = link_to "http://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do
+          = icon('linkedin-square')
     - unless @user.twitter.blank?
-      &middot;
-      = link_to "Twitter", "http://www.twitter.com/#{@user.twitter}"
+      .profile-link-holder
+        = link_to "http://www.twitter.com/#{@user.twitter}", title: "Twitter" do
+          = icon('twitter-square')
     - unless @user.website_url.blank?
-      &middot;
-      = link_to @user.short_website_url, @user.full_website_url
+      .profile-link-holder
+        = link_to @user.short_website_url, @user.full_website_url
     - unless @user.location.blank?
-      &middot;
-      = @user.location
+      .profile-link-holder
+        = icon('map-marker')
+        = @user.location
 
 
   .cover-controls
@@ -47,7 +52,7 @@
       = link_to profile_path, class: 'btn btn-gray' do
         = icon('pencil')
     - elsif current_user
-      .report-abuse
+      %span.report-abuse
         - if @user.abuse_report
           %button.btn.btn-danger{ title: 'Already reported for abuse',
             data: { toggle: 'tooltip', placement: 'left', container: 'body' }}
@@ -56,6 +61,10 @@
           = link_to new_abuse_report_path(user_id: @user.id), class: 'btn btn-gray',
             title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do
             = icon('exclamation-circle')
+    - if current_user
+      &nbsp;
+      = link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do
+        = icon('rss')
 
 .gray-content-block.second-block
   .user-calendar
@@ -64,27 +73,47 @@
   .user-calendar-activities
 
 
-.row.prepend-top-20
-  %section.col-md-7
-    - if @groups.any?
-      .prepend-top-20
-        %h4 Groups
-        = render 'groups', groups: @groups
-        %hr
-
-    %h4
-      User Activity
-
-      - if current_user
-        %span.rss-icon.pull-right
-          = link_to user_path(@user, :atom, { private_token: current_user.private_token }) do
-            %strong
-              %i.fa.fa-rss
+%ul.nav.center-middle-menu
+  %li.active
+    = link_to "#activity", 'data-toggle' => 'tab' do
+      Activity
+  - if @groups.any?
+    %li
+      = link_to "#groups", 'data-toggle' => 'tab' do
+        Groups
+  - if @contributed_projects.present?
+    %li
+      = link_to "#contributed", 'data-toggle' => 'tab' do
+        Contributed projects
+  - if @projects.present?
+    %li
+      = link_to "#personal", 'data-toggle' => 'tab' do
+        Personal projects
 
+.tab-content
+  .tab-pane.active#activity
     .content_list
     = spinner
-  %aside.col-md-5
-    = render 'projects', projects: @projects, contributed_projects: @contributed_projects
+
+  - if @groups.any?
+    .tab-pane#groups
+      %ul.content-list
+        - @groups.each do |group|
+          = render 'shared/groups/group', group: group
+
+  - if @contributed_projects.present?
+    .tab-pane#contributed
+      .contributed-projects
+        = render 'shared/projects/list',
+          projects: @contributed_projects.sort_by(&:star_count).reverse,
+          projects_limit: 5, stars: true, avatar: false
+
+  - if @projects.present?
+    .tab-pane#personal
+      .personal-projects
+        = render 'shared/projects/list',
+          projects: @projects.sort_by(&:star_count).reverse,
+          projects_limit: 10, stars: true, avatar: false
 
 :coffeescript
   $(".user-calendar").load("#{user_calendar_path}")
diff --git a/app/workers/stuck_ci_builds_worker.rb b/app/workers/stuck_ci_builds_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ad02a3b16d953b46eb5901e2be171a2211aa4382
--- /dev/null
+++ b/app/workers/stuck_ci_builds_worker.rb
@@ -0,0 +1,18 @@
+class StuckCiBuildsWorker
+  include Sidekiq::Worker
+  include Sidetiq::Schedulable
+
+  BUILD_STUCK_TIMEOUT = 1.day
+
+  recurrence { daily }
+
+  def perform
+    Rails.logger.info 'Cleaning stuck builds'
+
+    builds = Ci::Build.running_or_pending.where('updated_at < ?', BUILD_STUCK_TIMEOUT.ago)
+    builds.find_each(batch_size: 50).each do |build|
+      Rails.logger.debug "Dropping stuck #{build.status} build #{build.id} for runner #{build.runner_id}"
+      build.drop
+    end
+  end
+end
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
index 5d46ece1e1b27791215a48e141697abc40860dbb..9e8b0131f8ff7e3d3923697eca0b0ffdc77b19ce 100644
--- a/config/initializers/inflections.rb
+++ b/config/initializers/inflections.rb
@@ -8,24 +8,3 @@
 #   inflect.irregular 'person', 'people'
 #   inflect.uncountable %w( fish sheep )
 # end
-
-# Mark "commits" as uncountable.
-#
-# Without this change, the routes
-#
-#   resources :commit,  only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
-#   resources :commits, only: [:show], constraints: {id: /.+/}
-#
-# would generate identical route helper methods (`project_commit_path`), resulting
-# in one of them not getting a helper method at all.
-#
-# After this change, the helper methods are:
-#
-#   project_commit_path(@project, @project.commit)
-#   # => "/gitlabhq/commit/bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a
-#
-#   project_commits_path(@project, 'stable/README.md')
-#   # => "/gitlabhq/commits/stable/README.md"
-ActiveSupport::Inflector.inflections do |inflect|
-  inflect.uncountable %w(commits)
-end
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index c8122fc63b30016aecad548cfa6bb5f441f68900..1cf41aea391f32c4af004c5aed31f2456b486b7c 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -1,5 +1,5 @@
 # Build script examples
 
-+ [Test and deploy Ruby applications to Heroku](test-and-deploy-ruby-application-to-heroku.md)
-+ [Test and deploy Python applications to Heroku](test-and-deploy-python-application-to-heroku.md)
-+ [Test Clojure applications](test-clojure-application.md)
++ [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
++ [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
++ [Test a Clojure application](test-clojure-application.md)
diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
index 036b03dd6b922dbba270ab159e13f0b5ed44a793..a236da53fe9e03907d694f05f90c4913228f4430 100644
--- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
@@ -1,7 +1,7 @@
 ## Test and Deploy a python application
 This example will guide you how to run tests in your Python application and deploy it automatically as Heroku application.
 
-You can checkout the example [source](https://gitlab.com/ayufan/python-getting-started) and check [CI status](https://ci.gitlab.com/projects/4080).
+You can checkout the example [source](https://gitlab.com/ayufan/python-getting-started) and check [CI status](https://gitlab.com/ayufan/python-getting-started/builds?scope=all).
 
 ### Configure project
 This is what the `.gitlab-ci.yml` file looks like for this project:
diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
index d2a872f1934895e321c625c644c1ae483aa6db1f..e52e1547461d974306cc0393a053f86685dcdbe9 100644
--- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
@@ -1,7 +1,7 @@
 ## Test and Deploy a ruby application
 This example will guide you how to run tests in your Ruby application and deploy it automatiacally as Heroku application.
 
-You can checkout the example [source](https://gitlab.com/ayufan/ruby-getting-started) and check [CI status](https://ci.gitlab.com/projects/4050).
+You can checkout the example [source](https://gitlab.com/ayufan/ruby-getting-started) and check [CI status](https://gitlab.com/ayufan/ruby-getting-started/builds?scope=all).
 
 ### Configure project
 This is what the `.gitlab-ci.yml` file looks like for this project:
@@ -64,4 +64,4 @@ gitlab-ci-multi-runner register \
 
 With the command above, you create a runner that uses [ruby:2.1](https://registry.hub.docker.com/u/library/ruby/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database.
 
-To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password.
\ No newline at end of file
+To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password.
diff --git a/doc/ci/examples/test-clojure-application.md b/doc/ci/examples/test-clojure-application.md
index eaee94a10f13ccac2677ff76b423a8b06821f930..56b746ce0252b9e25d41a77ef86bcd2104b66bbb 100644
--- a/doc/ci/examples/test-clojure-application.md
+++ b/doc/ci/examples/test-clojure-application.md
@@ -1,8 +1,8 @@
-## Test Clojure applications
+## Test a Clojure application
 
 This example will guide you how to run tests in your Clojure application.
 
-You can checkout the example [source](https://gitlab.com/dzaporozhets/clojure-web-application) and check [CI status](https://ci.gitlab.com/projects/6306).
+You can checkout the example [source](https://gitlab.com/dzaporozhets/clojure-web-application) and check [CI status](https://gitlab.com/dzaporozhets/clojure-web-application/builds?scope=all).
 
 ### Configure project
 
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index ea8f72bc1358e38831d50c6796e63c3c58391fee..d117a2969be1b3d643004a7c0efd2d7138036a51 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -169,7 +169,7 @@ This are two parameters that allow for setting a refs policy to limit when jobs
 
 There are a few rules that apply to usage of refs policy:
 
-1. `only` and `except` are exclusive. If both `only` and `except` are defined in job specification only `only` is taken into account.
+1. `only` and `except` are inclusive. If both `only` and `except` are defined in job specification the ref is filtered by `only` and `except`.
 1. `only` and `except` allow for using the regexp expressions.
 1. `only` and `except` allow for using special keywords: `branches` and `tags`.
 These names can be used for example to exclude all tags and all branches.
@@ -182,6 +182,18 @@ job:
     - branches # use special keyword
 ```
 
+1. `only` and `except` allow for specify repository path to filter jobs for forks.
+The repository path can be used to have jobs executed only for parent repository.
+
+```yaml
+job:
+  only:
+    - branches@gitlab-org/gitlab-ce
+  except:
+    - master@gitlab-org/gitlab-ce
+```
+The above will run `job` for all branches on `gitlab-org/gitlab-ce`, except master .
+
 ### tags
 `tags` is used to select specific runners from the list of all runners that are allowed to run this project.
 
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index ef99a69f60a81bb21e5553cc7bee73cce5ab092a..7d838187a265fd7b2889e76409105ea70440ae66 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -69,7 +69,10 @@ X-Gitlab-Event: Push Hook
       }
     }
   ],
-  "total_commits_count": 4
+  "total_commits_count": 4,
+  "added": ["CHANGELOG"],
+  "modified": ["app/controller/application.rb"],
+  "removed": []
 }
 ```
 
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index 27c0bde364e989f9267c27dbfd528e4abd556331..168d9d30b508d23017d4edc6682086870fdd9da1 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -7,6 +7,7 @@ Feature: Profile
     Given I visit profile page
     Then I should see my profile info
 
+  @javascript
   Scenario: I can see groups I belong to
     Given I have group with projects
     When I visit profile page
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 69ddfa42c062df0d9f9b3d49b0c6bdd889d6ec46..70388c18fcf637e31861d7919a79e9bfdcfc5053 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -6,7 +6,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
   include Select2Helper
 
   step 'I should see back to dashboard button' do
-    expect(page).to have_content 'Back to dashboard'
+    expect(page).to have_content 'Go to dashboard'
   end
 
   step 'gitlab user "Mike"' do
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 8cf24705a5ecc15d5afc8b8a240ad4d11a99ed0a..40b2aa7c357761bde578679b678d1bf39a1cb6ae 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -59,7 +59,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
   step 'I should not see the "Remove avatar" button' do
     expect(page).not_to have_link("Remove avatar")
   end
-  
+
   step 'I should see the gravatar host link' do
     expect(page).to have_link("gravatar.com")
   end
@@ -159,10 +159,9 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
   end
 
   step 'I should see my user page' do
-    expect(page).to have_content "User Activity"
-
-    page.within '.navbar-gitlab' do
+    page.within ".cover-block" do
       expect(page).to have_content current_user.name
+      expect(page).to have_content current_user.username
     end
   end
 
@@ -176,7 +175,13 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
   end
 
   step 'I should see groups I belong to' do
-    expect(page).to have_css('.profile-groups-avatars', visible: true)
+    page.within ".content" do
+      click_link "Groups"
+    end
+
+    page.within "#groups" do
+      expect(page).to have_content @group.name
+    end
   end
 
   step 'I click on new application button' do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index d76891d5bdebb426d8ace148e69bee65cf2aafa8..9ca7c8ebbc7ef8ed8efcbeb3f7f137d14e16c5eb 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -124,11 +124,11 @@ class Spinach::Features::Project < Spinach::FeatureSteps
   end
 
   step 'I should see back to dashboard button' do
-    expect(page).to have_content 'Back to dashboard'
+    expect(page).to have_content 'Go to dashboard'
   end
 
   step 'I should see back to group button' do
-    expect(page).to have_content 'Back to group'
+    expect(page).to have_content 'Go to group'
   end
 
   step 'I click notifications drop down button' do
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index c67e5e4a06a890d5acbac404b4e0db3db09e7dd1..33ff7084e30df9f45eb6dc46846f0d940918a4f9 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -46,7 +46,7 @@ module SharedProjectTab
 
   step 'the active main tab should be Settings' do
     page.within '.nav-sidebar' do
-      expect(page).to have_content('Back to project')
+      expect(page).to have_content('Go to project')
     end
   end
 
diff --git a/lib/backup/builds.rb b/lib/backup/builds.rb
index 800f30c21449b129640d04beae29b3b336ce7bce..635967f4bd465949847382d6e0b030b07c9144e5 100644
--- a/lib/backup/builds.rb
+++ b/lib/backup/builds.rb
@@ -1,3 +1,5 @@
+require 'backup/files'
+
 module Backup
   class Builds < Files
     def initialize
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index 0a0ec564ba4f63db3ac6561cdc2def184fcb062c..9261f77f3c9d45ee137dd450ba5a6c401267c592 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -1,3 +1,5 @@
+require 'backup/files'
+
 module Backup
   class Uploads < Files
 
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index e602cda81d610402356e8e0654752c8ddecb1fdf..7e4986b6af35aaaab4078ceda5f55c9932d8a5b3 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -16,7 +16,9 @@ module Ci
       end
 
       def update_runner_last_contact
-        if current_runner.contacted_at.nil? || Time.now - current_runner.contacted_at >= UPDATE_RUNNER_EVERY
+        # Use a random threshold to prevent beating DB updates
+        contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY)
+        if current_runner.contacted_at.nil? || Time.now - current_runner.contacted_at >= contacted_at_max_age
           current_runner.update_attributes(contacted_at: Time.now)
         end
       end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index efcd2faffc74b143ffb1ad12904160195585fe1a..0f57a4f53ab23170d1b33176ed744c883016e6a3 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -7,10 +7,11 @@ module Ci
     ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables]
     ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when]
 
-    attr_reader :before_script, :image, :services, :variables
+    attr_reader :before_script, :image, :services, :variables, :path
 
-    def initialize(config)
+    def initialize(config, path = nil)
       @config = YAML.load(config)
+      @path = path
 
       unless @config.is_a? Hash
         raise ValidationError, "YAML should be a hash"
@@ -63,26 +64,6 @@ module Ci
       end
     end
 
-    def process?(only_params, except_params, ref, tag)
-      return true if only_params.nil? && except_params.nil?
-
-      if only_params
-        return true if tag && only_params.include?("tags")
-        return true if !tag && only_params.include?("branches")
-        
-        only_params.find do |pattern|
-          match_ref?(pattern, ref)
-        end
-      else
-        return false if tag && except_params.include?("tags")
-        return false if !tag && except_params.include?("branches")
-
-        except_params.each do |pattern|
-          return false if match_ref?(pattern, ref)
-        end
-      end
-    end
-
     def build_job(name, job)
       {
         stage_idx: stages.index(job[:stage]),
@@ -101,14 +82,6 @@ module Ci
       }
     end
 
-    def match_ref?(pattern, ref)
-      if pattern.first == "/" && pattern.last == "/"
-        Regexp.new(pattern[1...-1]) =~ ref
-      else
-        pattern == ref
-      end
-    end
-
     def normalize_script(script)
       if script.is_a? Array
         script.join("\n")
@@ -208,5 +181,36 @@ module Ci
     def validate_string(value)
       value.is_a?(String) || value.is_a?(Symbol)
     end
+
+    def process?(only_params, except_params, ref, tag)
+      if only_params.present?
+        return false unless matching?(only_params, ref, tag)
+      end
+
+      if except_params.present?
+        return false if matching?(except_params, ref, tag)
+      end
+
+      true
+    end
+
+    def matching?(patterns, ref, tag)
+      patterns.any? do |pattern|
+        match_ref?(pattern, ref, tag)
+      end
+    end
+
+    def match_ref?(pattern, ref, tag)
+      pattern, path = pattern.split('@', 2)
+      return false if path && path != self.path
+      return true if tag && pattern == 'tags'
+      return true if !tag && pattern == 'branches'
+
+      if pattern.first == "/" && pattern.last == "/"
+        Regexp.new(pattern[1...-1]) =~ ref
+      else
+        pattern == ref
+      end
+    end
   end
 end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 0a2be605af9bf656505d00e6602c0c161d3ec8cd..70de6a74e767a928fde2549f9582f22135642bdf 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -20,6 +20,8 @@ module Gitlab
         Kaminari.paginate_array(blobs).page(page).per(per_page)
       when 'wiki_blobs'
         Kaminari.paginate_array(wiki_blobs).page(page).per(per_page)
+      when 'commits'
+        Kaminari.paginate_array(commits).page(page).per(per_page)
       else
         super
       end
@@ -27,7 +29,7 @@ module Gitlab
 
     def total_count
       @total_count ||= issues_count + merge_requests_count + blobs_count +
-                       notes_count + wiki_blobs_count
+                       notes_count + wiki_blobs_count + commits_count
     end
 
     def blobs_count
@@ -42,6 +44,10 @@ module Gitlab
       @wiki_blobs_count ||= wiki_blobs.count
     end
 
+    def commits_count
+      @commits_count ||= commits.count
+    end
+
     private
 
     def blobs
@@ -70,6 +76,14 @@ module Gitlab
       Note.where(project_id: limit_project_ids).user.search(query).order('updated_at DESC')
     end
 
+    def commits
+      if project.empty_repo? || query.blank?
+        []
+      else
+        project.repository.find_commits_by_message(query).compact
+      end
+    end
+
     def limit_project_ids
       [project.id]
     end
diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
index d010ade704e0eb7a6e4de974e2548a20c38bc2ce..fa068d507634a1351985bab5e2877dfd8484e1e3 100644
--- a/lib/gitlab/push_data_builder.rb
+++ b/lib/gitlab/push_data_builder.rb
@@ -18,7 +18,10 @@ module Gitlab
       #     homepage: String,
       #   },
       #   commits: Array,
-      #   total_commits_count: Fixnum
+      #   total_commits_count: Fixnum,
+      #   added: ["CHANGELOG"],
+      #   modified: [],
+      #   removed: ["tmp/file.txt"]
       # }
       #
       def build(project, user, oldrev, newrev, ref, commits = [], message = nil)
@@ -33,6 +36,8 @@ module Gitlab
         commit_attrs = commits_limited.map(&:hook_attrs)
 
         type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push"
+
+        repo_changes = repo_changes(project, newrev, oldrev)
         # Hash to be passed as post_receive_data
         data = {
           object_kind: type,
@@ -55,7 +60,10 @@ module Gitlab
             visibility_level: project.visibility_level
           },
           commits: commit_attrs,
-          total_commits_count: commits_count
+          total_commits_count: commits_count,
+          added: repo_changes[:added],
+          modified: repo_changes[:modified],
+          removed: repo_changes[:removed]
         }
 
         data
@@ -86,6 +94,27 @@ module Gitlab
           newrev
         end
       end
+
+      def repo_changes(project, newrev, oldrev)
+        changes = { added: [], modified: [], removed: [] }
+        compare_result = CompareService.new.
+          execute(project, newrev, project, oldrev)
+
+        if compare_result
+          compare_result.diffs.each do |diff|
+            case true
+            when diff.deleted_file
+              changes[:removed] << diff.old_path
+            when diff.renamed_file, diff.new_file
+              changes[:added] << diff.new_path
+            else
+              changes[:modified] << diff.new_path
+            end
+          end
+        end
+
+        changes
+      end
     end
   end
 end
diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake
index c8881be0954cefb61356af6adff0b830976807b2..d5a96fd38f4776459c55ab2d7779625f14e92a23 100644
--- a/lib/tasks/spinach.rake
+++ b/lib/tasks/spinach.rake
@@ -5,7 +5,7 @@ namespace :spinach do
   task :project do
     cmds = [
       %W(rake gitlab:setup),
-      %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets),
+      %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets,~@commits),
     ]
     run_commands(cmds)
   end
@@ -14,7 +14,7 @@ namespace :spinach do
   task :other do
     cmds = [
       %W(rake gitlab:setup),
-      %W(spinach --tags @admin,@dashboard,@profile,@public,@snippets),
+      %W(spinach --tags @admin,@dashboard,@profile,@public,@snippets,@commits),
     ]
     run_commands(cmds)
   end
@@ -33,4 +33,4 @@ def run_commands(cmds)
   cmds.each do |cmd|
     system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd) or raise("#{cmd} failed!")
   end
-end
+end
\ No newline at end of file
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index abdb6b89ac55e592e7dd7cd4885a0eedcb5fdefd..9963f76f99303271d7e0dace685f3886dc5cf2f3 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -2,7 +2,8 @@ require 'spec_helper'
 
 module Ci
   describe GitlabCiYamlProcessor do
-
+    let(:path) { 'path' }
+    
     describe "#builds_for_ref" do
       let(:type) { 'test' }
 
@@ -12,7 +13,7 @@ module Ci
           rspec: { script: "rspec" }
         })
 
-        config_processor = GitlabCiYamlProcessor.new(config)
+        config_processor = GitlabCiYamlProcessor.new(config, path)
 
         expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
         expect(config_processor.builds_for_stage_and_ref(type, "master").first).to eq({
@@ -28,78 +29,218 @@ module Ci
           when: "on_success"
         })
       end
+      
+      describe :only do
+        it "does not return builds if only has another branch" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", only: ["deploy"] }
+                             })
 
-      it "does not return builds if only has another branch" do
-        config = YAML.dump({
-          before_script: ["pwd"],
-          rspec: { script: "rspec", only: ["deploy"] }
-        })
+          config_processor = GitlabCiYamlProcessor.new(config, path)
 
-        config_processor = GitlabCiYamlProcessor.new(config)
+          expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
+        end
 
-        expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
-      end
+        it "does not return builds if only has regexp with another branch" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", only: ["/^deploy$/"] }
+                             })
 
-      it "does not return builds if only has regexp with another branch" do
-        config = YAML.dump({
-          before_script: ["pwd"],
-          rspec: { script: "rspec", only: ["/^deploy$/"] }
-        })
+          config_processor = GitlabCiYamlProcessor.new(config, path)
 
-        config_processor = GitlabCiYamlProcessor.new(config)
+          expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
+        end
 
-        expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
-      end
+        it "returns builds if only has specified this branch" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", only: ["master"] }
+                             })
 
-      it "returns builds if only has specified this branch" do
-        config = YAML.dump({
-          before_script: ["pwd"],
-          rspec: { script: "rspec", only: ["master"] }
-        })
+          config_processor = GitlabCiYamlProcessor.new(config, path)
 
-        config_processor = GitlabCiYamlProcessor.new(config)
+          expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
+        end
 
-        expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
-      end
+        it "returns builds if only has a list of branches including specified" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", type: type, only: ["master", "deploy"] }
+                             })
 
-      it "does not build tags" do
-        config = YAML.dump({
-          before_script: ["pwd"],
-          rspec: { script: "rspec", except: ["tags"] }
-        })
+          config_processor = GitlabCiYamlProcessor.new(config, path)
 
-        config_processor = GitlabCiYamlProcessor.new(config)
+          expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+        end
 
-        expect(config_processor.builds_for_stage_and_ref(type, "0-1", true).size).to eq(0)
-      end
+        it "returns builds if only has a branches keyword specified" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", type: type, only: ["branches"] }
+                             })
 
-      it "returns builds if only has a list of branches including specified" do
-        config = YAML.dump({
-                             before_script: ["pwd"],
-                             rspec: { script: "rspec", type: type, only: ["master", "deploy"] }
-                           })
+          config_processor = GitlabCiYamlProcessor.new(config, path)
+
+          expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+        end
+
+        it "does not return builds if only has a tags keyword" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", type: type, only: ["tags"] }
+                             })
+
+          config_processor = GitlabCiYamlProcessor.new(config, path)
+
+          expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
+        end
+
+        it "returns builds if only has current repository path" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", type: type, only: ["branches@path"] }
+                             })
+
+          config_processor = GitlabCiYamlProcessor.new(config, path)
+
+          expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+        end
+
+        it "does not return builds if only has different repository path" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", type: type, only: ["branches@fork"] }
+                             })
+
+          config_processor = GitlabCiYamlProcessor.new(config, path)
+
+          expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
+        end
+
+        it "returns build only for specified type" do
+
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", type: "test", only: ["master", "deploy"] },
+                               staging: { script: "deploy", type: "deploy", only: ["master", "deploy"] },
+                               production: { script: "deploy", type: "deploy", only: ["master@path", "deploy"] },
+                             })
 
-        config_processor = GitlabCiYamlProcessor.new(config)
+          config_processor = GitlabCiYamlProcessor.new(config, 'fork')
 
-        expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+          expect(config_processor.builds_for_stage_and_ref("deploy", "deploy").size).to eq(2)
+          expect(config_processor.builds_for_stage_and_ref("test", "deploy").size).to eq(1)
+          expect(config_processor.builds_for_stage_and_ref("deploy", "master").size).to eq(1)
+        end
       end
 
-      it "returns build only for specified type" do
+      describe :except do
+        it "returns builds if except has another branch" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", except: ["deploy"] }
+                             })
 
-        config = YAML.dump({
-                             before_script: ["pwd"],
-                             build: { script: "build", type: "build", only: ["master", "deploy"] },
-                             rspec: { script: "rspec", type: type, only: ["master", "deploy"] },
-                             staging: { script: "deploy", type: "deploy", only: ["master", "deploy"] },
-                             production: { script: "deploy", type: "deploy", only: ["master", "deploy"] },
-                           })
+          config_processor = GitlabCiYamlProcessor.new(config, path)
 
-        config_processor = GitlabCiYamlProcessor.new(config)
+          expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
+        end
+
+        it "returns builds if except has regexp with another branch" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", except: ["/^deploy$/"] }
+                             })
+
+          config_processor = GitlabCiYamlProcessor.new(config, path)
+
+          expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
+        end
 
-        expect(config_processor.builds_for_stage_and_ref("production", "deploy").size).to eq(0)
-        expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
-        expect(config_processor.builds_for_stage_and_ref("deploy", "deploy").size).to eq(2)
+        it "does not return builds if except has specified this branch" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", except: ["master"] }
+                             })
+
+          config_processor = GitlabCiYamlProcessor.new(config, path)
+
+          expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(0)
+        end
+
+        it "does not return builds if except has a list of branches including specified" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", type: type, except: ["master", "deploy"] }
+                             })
+
+          config_processor = GitlabCiYamlProcessor.new(config, path)
+
+          expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
+        end
+
+        it "does not return builds if except has a branches keyword specified" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", type: type, except: ["branches"] }
+                             })
+
+          config_processor = GitlabCiYamlProcessor.new(config, path)
+
+          expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
+        end
+
+        it "returns builds if except has a tags keyword" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", type: type, except: ["tags"] }
+                             })
+
+          config_processor = GitlabCiYamlProcessor.new(config, path)
+
+          expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+        end
+
+        it "does not return builds if except has current repository path" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", type: type, except: ["branches@path"] }
+                             })
+
+          config_processor = GitlabCiYamlProcessor.new(config, path)
+
+          expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(0)
+        end
+
+        it "returns builds if except has different repository path" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", type: type, except: ["branches@fork"] }
+                             })
+
+          config_processor = GitlabCiYamlProcessor.new(config, path)
+
+          expect(config_processor.builds_for_stage_and_ref(type, "deploy").size).to eq(1)
+        end
+
+        it "returns build except specified type" do
+          config = YAML.dump({
+                               before_script: ["pwd"],
+                               rspec: { script: "rspec", type: "test", except: ["master", "deploy", "test@fork"] },
+                               staging: { script: "deploy", type: "deploy", except: ["master"] },
+                               production: { script: "deploy", type: "deploy", except: ["master@fork"] },
+                             })
+
+          config_processor = GitlabCiYamlProcessor.new(config, 'fork')
+
+          expect(config_processor.builds_for_stage_and_ref("deploy", "deploy").size).to eq(2)
+          expect(config_processor.builds_for_stage_and_ref("test", "test").size).to eq(0)
+          expect(config_processor.builds_for_stage_and_ref("deploy", "master").size).to eq(0)
+        end
       end
+
     end
 
     describe "Image and service handling" do
@@ -111,7 +252,7 @@ module Ci
                              rspec: { script: "rspec" }
                            })
 
-        config_processor = GitlabCiYamlProcessor.new(config)
+        config_processor = GitlabCiYamlProcessor.new(config, path)
 
         expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
         expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
@@ -139,7 +280,7 @@ module Ci
                              rspec:         { image: "ruby:2.5", services: ["postgresql"], script: "rspec" }
                            })
 
-        config_processor = GitlabCiYamlProcessor.new(config)
+        config_processor = GitlabCiYamlProcessor.new(config, path)
 
         expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
         expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
@@ -172,7 +313,7 @@ module Ci
                              rspec: { script: "rspec" }
                            })
 
-        config_processor = GitlabCiYamlProcessor.new(config)
+        config_processor = GitlabCiYamlProcessor.new(config, path)
         expect(config_processor.variables).to eq(variables)
       end
     end
@@ -184,7 +325,7 @@ module Ci
                                rspec: { script: "rspec", when: when_state }
                              })
 
-          config_processor = GitlabCiYamlProcessor.new(config)
+          config_processor = GitlabCiYamlProcessor.new(config, path)
           builds = config_processor.builds_for_stage_and_ref("test", "master")
           expect(builds.size).to eq(1)
           expect(builds.first[:when]).to eq(when_state)
@@ -200,154 +341,154 @@ module Ci
       it "returns errors if tags parameter is invalid" do
         config = YAML.dump({ rspec: { script: "test", tags: "mysql" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: tags parameter should be an array of strings")
       end
 
       it "returns errors if before_script parameter is invalid" do
         config = YAML.dump({ before_script: "bundle update", rspec: { script: "test" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "before_script should be an array of strings")
       end
 
       it "returns errors if image parameter is invalid" do
         config = YAML.dump({ image: ["test"], rspec: { script: "test" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image should be a string")
       end
 
       it "returns errors if job name is blank" do
         config = YAML.dump({ '' => { script: "test" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string")
       end
 
       it "returns errors if job name is non-string" do
         config = YAML.dump({ 10 => { script: "test" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string")
       end
 
       it "returns errors if job image parameter is invalid" do
         config = YAML.dump({ rspec: { script: "test", image: ["test"] } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: image should be a string")
       end
 
       it "returns errors if services parameter is not an array" do
         config = YAML.dump({ services: "test", rspec: { script: "test" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services should be an array of strings")
       end
 
       it "returns errors if services parameter is not an array of strings" do
         config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services should be an array of strings")
       end
 
       it "returns errors if job services parameter is not an array" do
         config = YAML.dump({ rspec: { script: "test", services: "test" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings")
       end
 
       it "returns errors if job services parameter is not an array of strings" do
         config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings")
       end
 
       it "returns errors if there are unknown parameters" do
         config = YAML.dump({ extra: "bundle update" })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra")
       end
 
       it "returns errors if there are unknown parameters that are hashes, but doesn't have a script" do
         config = YAML.dump({ extra: { services: "test" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra")
       end
 
       it "returns errors if there is no any jobs defined" do
         config = YAML.dump({ before_script: ["bundle update"] })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Please define at least one job")
       end
 
       it "returns errors if job allow_failure parameter is not an boolean" do
         config = YAML.dump({ rspec: { script: "test", allow_failure: "string" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: allow_failure parameter should be an boolean")
       end
 
       it "returns errors if job stage is not a string" do
         config = YAML.dump({ rspec: { script: "test", type: 1, allow_failure: "string" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy")
       end
 
       it "returns errors if job stage is not a pre-defined stage" do
         config = YAML.dump({ rspec: { script: "test", type: "acceptance", allow_failure: "string" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy")
       end
 
       it "returns errors if job stage is not a defined stage" do
         config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", type: "acceptance", allow_failure: "string" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test")
       end
 
       it "returns errors if stages is not an array" do
         config = YAML.dump({ types: "test", rspec: { script: "test" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "stages should be an array of strings")
       end
 
       it "returns errors if stages is not an array of strings" do
         config = YAML.dump({ types: [true, "test"], rspec: { script: "test" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "stages should be an array of strings")
       end
 
       it "returns errors if variables is not a map" do
         config = YAML.dump({ variables: "test", rspec: { script: "test" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings")
       end
 
       it "returns errors if variables is not a map of key-valued strings" do
         config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings")
       end
 
       it "returns errors if job when is not on_success, on_failure or always" do
         config = YAML.dump({ rspec: { script: "test", when: 1 } })
         expect do
-          GitlabCiYamlProcessor.new(config)
+          GitlabCiYamlProcessor.new(config, path)
         end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always")
       end
     end
diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb
index 1b8ba7b4d4333d300f62845d8bfccaf1060ced1a..02710742625d18a20242d63b5f721a9b8c5ac73e 100644
--- a/spec/lib/gitlab/push_data_builder_spec.rb
+++ b/spec/lib/gitlab/push_data_builder_spec.rb
@@ -17,6 +17,9 @@ describe 'Gitlab::PushDataBuilder' do
     it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) }
     it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) }
     it { expect(data[:total_commits_count]).to eq(3) }
+    it { expect(data[:added]).to eq(["gitlab-grack"]) }
+    it { expect(data[:modified]).to eq([".gitmodules", "files/ruby/popen.rb", "files/ruby/regex.rb"]) }
+    it { expect(data[:removed]).to eq([]) }
   end
 
   describe :build do
@@ -35,5 +38,8 @@ describe 'Gitlab::PushDataBuilder' do
     it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
     it { expect(data[:commits]).to be_empty }
     it { expect(data[:total_commits_count]).to be_zero }
+    it { expect(data[:added]).to eq([]) }
+    it { expect(data[:modified]).to eq([]) }
+    it { expect(data[:removed]).to eq([]) }
   end
 end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index cb67ec95d57469d11f09eb9b7b28422a66c3cfe7..47863d54579e05959f2c5930d89bf2352d8f695e 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -468,7 +468,7 @@ describe Notify do
         subject { Notify.note_commit_email(recipient.id, note.id) }
 
         it_behaves_like 'a note email'
-        it_behaves_like 'an answer to an existing thread', 'commits'
+        it_behaves_like 'an answer to an existing thread', 'commit'
 
         it 'has the correct subject' do
           is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 05e51532eb878564afd1ad0db0f73cd2e634dcb3..319fa0a7c8d70cae60ade1943744b1b8c6581ea9 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -26,6 +26,15 @@ describe Repository do
     it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
   end
 
+  describe :find_commits_by_message do
+    subject { repository.find_commits_by_message('submodule').map{ |k| k.id } }
+
+    it { is_expected.to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
+    it { is_expected.to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
+    it { is_expected.to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660') }
+    it { is_expected.not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') }
+  end
+
   describe :blob_at do
     context 'blank sha' do
       subject { repository.blob_at(Gitlab::Git::BLANK_SHA, '.gitignore') }
diff --git a/spec/workers/stuck_ci_builds_worker_spec.rb b/spec/workers/stuck_ci_builds_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f9d87d97014f94e9e791882bbc7dfe6b5a875f66
--- /dev/null
+++ b/spec/workers/stuck_ci_builds_worker_spec.rb
@@ -0,0 +1,44 @@
+require "spec_helper"
+
+describe StuckCiBuildsWorker do
+  let!(:build) { create :ci_build }
+
+  subject do
+    build.reload
+    build.status
+  end
+
+  %w(pending running).each do |status|
+    context "#{status} build" do
+      before do
+        build.update!(status: status)
+      end
+
+      it 'gets dropped if it was updated over 2 days ago' do
+        build.update!(updated_at: 2.day.ago)
+        StuckCiBuildsWorker.new.perform
+        is_expected.to eq('failed')
+      end
+
+      it "is still #{status}" do
+        build.update!(updated_at: 1.minute.ago)
+        StuckCiBuildsWorker.new.perform
+        is_expected.to eq(status)
+      end
+    end
+  end
+
+  %w(success failed canceled).each do |status|
+    context "#{status} build" do
+      before do
+        build.update!(status: status)
+      end
+
+      it "is still #{status}" do
+        build.update!(updated_at: 2.day.ago)
+        StuckCiBuildsWorker.new.perform
+        is_expected.to eq(status)
+      end
+    end
+  end
+end