diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 576fa3cedb2b60bcf6f4ef0d1dfc67ee79861716..f9a4aeaa627a65a68ceb350b19474745cfd6b225 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -94,8 +94,8 @@ class Projects::CommitController < Projects::ApplicationController
     @commit ||= @project.commit(params[:id])
   end
 
-  def ci_commit
-    @ci_commit ||= project.ci_commit(commit.sha)
+  def ci_commits
+    @ci_commits ||= project.ci_commits.where(sha: commit.sha)
   end
 
   def define_show_vars
@@ -108,7 +108,8 @@ class Projects::CommitController < Projects::ApplicationController
     @diff_refs = [commit.parent || commit, commit]
     @notes_count = commit.notes.count
 
-    @statuses = ci_commit.statuses if ci_commit
+    @statuses = CommitStatus.where(commit: ci_commits)
+    @builds = Ci::Build.where(commit: ci_commits)
   end
 
   def assign_revert_commit_vars
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 49064f5d50570f6c4d6463797fbb87ae8c15a734..ed5a1901056decd020146d3fbb0b5abb5cc5f6cb 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -118,6 +118,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
 
     @ci_commit = @merge_request.ci_commit
+    @ci_commits = [@ci_commit].compact
     @statuses = @ci_commit.statuses if @ci_commit
 
     @note_counts = Note.where(commit_id: @commits.map(&:id)).
@@ -308,6 +309,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     @merge_request_diff = @merge_request.merge_request_diff
 
     @ci_commit = @merge_request.ci_commit
+    @ci_commits = [@ci_commit].compact
     @statuses = @ci_commit.statuses if @ci_commit
 
     if @merge_request.locked_long_ago?
@@ -318,6 +320,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
   def define_widget_vars
     @ci_commit = @merge_request.ci_commit
+    @ci_commits = [@ci_commit].compact
     closes_issues
   end
 
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 8b1575d5e0c5175ecfbf7a81b559f3756743bdf6..fd2179c7af53e9d622c2da094bb4ab1cb470e097 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -1,17 +1,4 @@
 module CiStatusHelper
-  def ci_status_path(ci_commit)
-    project = ci_commit.project
-    builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
-  end
-
-  def ci_status_icon(ci_commit)
-    ci_icon_for_status(ci_commit.status)
-  end
-
-  def ci_status_label(ci_commit)
-    ci_label_for_status(ci_commit.status)
-  end
-
   def ci_status_with_icon(status, target = nil)
     content = ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
     klass = "ci-status ci-#{status}"
@@ -47,10 +34,10 @@ module CiStatusHelper
   end
 
   def render_ci_status(ci_commit, tooltip_placement: 'auto left')
-    link_to ci_status_icon(ci_commit),
-      ci_status_path(ci_commit),
+    link_to ci_icon_for_status(ci_commit.status),
+      project_ci_commit_path(ci_commit.project, ci_commit),
       class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
-      title: "Build #{ci_status_label(ci_commit)}",
+      title: "Build #{ci_label_for_status(ci_commit.status)}",
       data: { toggle: 'tooltip', placement: tooltip_placement }
   end
 
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index f3fddef01cb8c0e7226779ebaebe5f66edf21eda..f1af8e163cdb8490f4ff4f38521887615a41b500 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -25,10 +25,22 @@ module GitlabRoutingHelper
     namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
   end
 
+  def project_pipelines_path(project, *args)
+    namespace_project_pipelines_path(project.namespace, project, *args)
+  end
+
   def project_builds_path(project, *args)
     namespace_project_builds_path(project.namespace, project, *args)
   end
 
+  def project_commit_path(project, commit)
+    builds_namespace_project_commit_path(project.namespace, project, commit.id)
+  end
+
+  def project_ci_commit_path(project, ci_commit)
+    builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
+  end
+
   def activity_project_path(project, *args)
     activity_namespace_project_path(project.namespace, project, *args)
   end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 7d33838044b556846c754bbfdd50917fd138d20f..15fc714b538b54dabf6de9c50a1bcea37db0470f 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -50,7 +50,6 @@ module Ci
 
     scope :unstarted, ->() { where(runner_id: nil) }
     scope :ignore_failures, ->() { where(allow_failure: false) }
-    scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
 
     mount_uploader :artifacts_file, ArtifactUploader
     mount_uploader :artifacts_metadata, ArtifactUploader
@@ -62,6 +61,8 @@ module Ci
 
     before_destroy { project }
 
+    after_create :execute_hooks
+
     class << self
       def columns_without_lazy
         (column_names - LAZY_ATTRIBUTES).map do |column_name|
@@ -126,12 +127,16 @@ module Ci
     end
 
     def retried?
-      !self.commit.latest_statuses_for_ref(self.ref).include?(self)
+      !self.commit.latest.include?(self)
+    end
+
+    def retry
+      Ci::Build.retry(self)
     end
 
     def depends_on_builds
       # Get builds of the same type
-      latest_builds = self.commit.builds.similar(self).latest
+      latest_builds = self.commit.builds.latest
 
       # Return builds from previous stages
       latest_builds.where('stage_idx < ?', stage_idx)
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index f4cf7034b14a3a37db58edba331cedc5bf88bb06..70fe63877cba7cab4285d0ad08923ae39bd4040b 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -19,6 +19,7 @@
 module Ci
   class Commit < ActiveRecord::Base
     extend Ci::Model
+    include CiStatus
 
     belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
     has_many :statuses, class_name: 'CommitStatus'
@@ -28,12 +29,18 @@ module Ci
     validates_presence_of :sha
     validate :valid_commit_sha
 
+    # Make sure that status is saved
+    before_save :status
+    before_save :started_at
+    before_save :finished_at
+    before_save :duration
+
     def self.truncate_sha(sha)
       sha[0...8]
     end
 
-    def to_param
-      sha
+    def stages
+      statuses.group(:stage).order(:stage_idx).pluck(:stage)
     end
 
     def project_id
@@ -68,15 +75,26 @@ module Ci
       nil
     end
 
-    def stage
-      running_or_pending = statuses.latest.running_or_pending.ordered
-      running_or_pending.first.try(:stage)
+    def branch?
+      !tag?
+    end
+
+    def retryable?
+      builds.latest.any? do |build|
+        build.failed? || build.retryable?
+      end
+    end
+
+    def invalidate
+      status = nil
+      started_at = nil
+      finished_at = nil
     end
 
-    def create_builds(ref, tag, user, trigger_request = nil)
+    def create_builds(user, trigger_request = nil)
       return unless config_processor
       config_processor.stages.any? do |stage|
-        CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present?
+        CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present?
       end
     end
 
@@ -84,7 +102,7 @@ module Ci
       return unless config_processor
 
       # don't create other builds if this one is retried
-      latest_builds = builds.similar(build).latest
+      latest_builds = builds.latest
       return unless latest_builds.exists?(build.id)
 
       # get list of stages after this build
@@ -97,26 +115,12 @@ module Ci
 
       # create builds for next stages based
       next_stages.any? do |stage|
-        CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present?
+        CreateBuildsService.new(self).execute(stage, build.user, status, build.trigger_request).present?
       end
     end
 
-    def refs
-      statuses.order(:ref).pluck(:ref).uniq
-    end
-
-    def latest_statuses
-      @latest_statuses ||= statuses.latest.to_a
-    end
-
-    def latest_statuses_for_ref(ref)
-      latest_statuses.select { |status| status.ref == ref }
-    end
-
-    def matrix_builds(build = nil)
-      matrix_builds = builds.latest.ordered
-      matrix_builds = matrix_builds.similar(build) if build
-      matrix_builds.to_a
+    def latest
+      statuses.latest
     end
 
     def retried
@@ -124,56 +128,23 @@ module Ci
     end
 
     def status
-      if yaml_errors.present?
-        return 'failed'
-      end
-
-      @status ||= Ci::Status.get_status(latest_statuses)
-    end
-
-    def pending?
-      status == 'pending'
-    end
-
-    def running?
-      status == 'running'
-    end
-
-    def success?
-      status == 'success'
-    end
-
-    def failed?
-      status == 'failed'
-    end
-
-    def canceled?
-      status == 'canceled'
-    end
-
-    def active?
-      running? || pending?
-    end
-
-    def complete?
-      canceled? || success? || failed?
+      read_attribute(:status) || update_status
     end
 
     def duration
-      duration_array = statuses.map(&:duration).compact
-      duration_array.reduce(:+).to_i
+      read_attribute(:duration) || update_duration
     end
 
     def started_at
-      @started_at ||= statuses.order('started_at ASC').first.try(:started_at)
+      read_attribute(:started_at) || update_started_at
     end
 
     def finished_at
-      @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
+      read_attribute(:finished_at) || update_finished_at
     end
 
     def coverage
-      coverage_array = latest_statuses.map(&:coverage).compact
+      coverage_array = latest.map(&:coverage).compact
       if coverage_array.size >= 1
         '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
       end
@@ -191,6 +162,7 @@ module Ci
     end
 
     def ci_yaml_file
+      return nil if defined?(@ci_yaml_file)
       @ci_yaml_file ||= begin
         blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
         blob.load_all_data!(project.repository)
@@ -206,6 +178,32 @@ module Ci
 
     private
 
+    def update_status
+      status =
+        if yaml_errors.present?
+          'failed'
+        else
+          latest.status
+        end
+    end
+
+    def update_started_at
+      started_at =
+        statuses.order(:id).first.try(:started_at)
+    end
+
+    def update_finished_at
+      finished_at =
+        statuses.order(id: :desc).first.try(:finished_at)
+    end
+
+    def update_duration
+      duration = begin
+        duration_array = latest.map(&:duration).compact
+        duration_array.reduce(:+).to_i
+      end
+    end
+
     def save_yaml_error(error)
       return if self.yaml_errors?
       self.yaml_errors = error
diff --git a/app/models/commit.rb b/app/models/commit.rb
index d09876a07d92842a5d93896fbe80f3251f75b0f1..a898f7ba337b96f62409870c86d5c20dcf71b0d6 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -209,12 +209,13 @@ class Commit
     @raw.short_id(7)
   end
 
-  def ci_commit
-    project.ci_commit(sha)
+  def ci_commits
+    @ci_commits ||= project.ci_commits.where(sha: sha)
   end
 
   def status
-    ci_commit.try(:status) || :not_found
+    return @status if defined?(@status)
+    @status ||= ci_commits.status
   end
 
   def revert_branch_name
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 3377a85a55a7a103a451d44baf6060398d76223b..da7d6ea6b94522dc22c5af80c161e9e2b9975b4d 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -33,6 +33,8 @@
 #
 
 class CommitStatus < ActiveRecord::Base
+  include CiStatus
+
   self.table_name = 'ci_builds'
 
   belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
@@ -40,21 +42,13 @@ class CommitStatus < ActiveRecord::Base
   belongs_to :user
 
   validates :commit, presence: true
-  validates :status, inclusion: { in: %w(pending running failed success canceled) }
 
   validates_presence_of :name
 
   alias_attribute :author, :user
 
-  scope :running, -> { where(status: 'running') }
-  scope :pending, -> { where(status: 'pending') }
-  scope :success, -> { where(status: 'success') }
-  scope :failed, -> { where(status: 'failed')  }
-  scope :running_or_pending, -> { where(status: [:running, :pending]) }
-  scope :finished, -> { where(status: [:success, :failed, :canceled]) }
-  scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
+  scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name)) }
   scope :ordered, -> { order(:ref, :stage_idx, :name) }
-  scope :for_ref, ->(ref) { where(ref: ref) }
 
   AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled']
 
@@ -87,31 +81,13 @@ class CommitStatus < ActiveRecord::Base
       MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
     end
 
-    state :pending, value: 'pending'
-    state :running, value: 'running'
-    state :failed, value: 'failed'
-    state :success, value: 'success'
-    state :canceled, value: 'canceled'
-  end
-
-  delegate :sha, :short_sha, to: :commit, prefix: false
-
-  # TODO: this should be removed with all references
-  def before_sha
-    Gitlab::Git::BLANK_SHA
-  end
-
-  def started?
-    !pending? && !canceled? && started_at
-  end
-
-  def active?
-    running? || pending?
+    after_transition any => any do |commit_status|
+      commit_status.commit.invalidate
+      commit_status.save
+    end
   end
 
-  def complete?
-    canceled? || success? || failed?
-  end
+  delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false
 
   def ignored?
     allow_failure? && (failed? || canceled?)
diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9fe20bc9d7344410fa5bac7a402312653fc6e04a
--- /dev/null
+++ b/app/models/concerns/ci_status.rb
@@ -0,0 +1,58 @@
+module CiStatus
+  extend ActiveSupport::Concern
+
+  module ClassMethods
+    def status
+      objs = all.to_a
+      if objs.none?
+         nil
+      elsif objs.all? { |status| status.success? || status.try(:ignored?) }
+        'success'
+      elsif objs.all?(&:pending?)
+        'pending'
+      elsif objs.any?(&:running?) || all.any?(&:pending?)
+        'running'
+      elsif objs.all?(&:canceled?)
+        'canceled'
+      else
+        'failed'
+      end
+    end
+
+    def duration
+      duration_array = all.map(&:duration).compact
+      duration_array.reduce(:+).to_i
+    end
+  end
+
+  included do
+    validates :status, inclusion: { in: %w(pending running failed success canceled) }
+
+    state_machine :status, initial: :pending do
+      state :pending, value: 'pending'
+      state :running, value: 'running'
+      state :failed, value: 'failed'
+      state :success, value: 'success'
+      state :canceled, value: 'canceled'
+    end
+
+    scope :running, -> { where(status: 'running') }
+    scope :pending, -> { where(status: 'pending') }
+    scope :success, -> { where(status: 'success') }
+    scope :failed, -> { where(status: 'failed')  }
+    scope :running_or_pending, -> { where(status: [:running, :pending]) }
+    scope :finished, -> { where(status: [:success, :failed, :canceled]) }
+  end
+
+  def started?
+    !pending? && !canceled? && started_at
+  end
+
+  def active?
+    running? || pending?
+  end
+
+  def complete?
+    canceled? || success? || failed?
+  end
+end
\ No newline at end of file
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index bf185cb5dd8bb52ba400bf2ab6c5168486bb70e6..33869e215c9396bcd70cbd333e1944ee73c15ffd 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -589,7 +589,7 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def ci_commit
-    @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
+    @ci_commit ||= source_project.ci_commit(last_commit.id, source_branch) if last_commit && source_project
   end
 
   def diff_refs
diff --git a/app/models/project.rb b/app/models/project.rb
index 3e1f04b41585627fd4e7e3ac0206c4587a905204..b9d589a4594bb587e29628e953ffee937e7624d9 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -919,12 +919,12 @@ class Project < ActiveRecord::Base
     !namespace.share_with_group_lock
   end
 
-  def ci_commit(sha)
-    ci_commits.find_by(sha: sha)
+  def ci_commit(sha, ref)
+    ci_commits.find_by(sha: sha, ref: ref)
   end
 
-  def ensure_ci_commit(sha)
-    ci_commit(sha) || ci_commits.create(sha: sha)
+  def ensure_ci_commit(sha, ref)
+    ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref)
   end
 
   def enable_ci
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
index 2cd51a7610f6310e5c0c098244e74e2ddba27e0a..3b6e045d698272e14782f0876e5c99e2f8a3598f 100644
--- a/app/services/ci/create_builds_service.rb
+++ b/app/services/ci/create_builds_service.rb
@@ -1,7 +1,11 @@
 module Ci
   class CreateBuildsService
-    def execute(commit, stage, ref, tag, user, trigger_request, status)
-      builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request)
+    def initialize(commit)
+      @commit = commit
+    end
+
+    def execute(stage, user, status, trigger_request = nil)
+      builds_attrs = config_processor.builds_for_stage_and_ref(stage, @commit.ref, @commit.tag, trigger_request)
 
       # check when to create next build
       builds_attrs = builds_attrs.select do |build_attrs|
@@ -17,7 +21,8 @@ module Ci
 
       builds_attrs.map do |build_attrs|
         # don't create the same build twice
-        unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
+        unless commit.builds.find_by(ref: @commit.ref, tag: @commit.tag,
+                                     trigger_request: trigger_request, name: build_attrs[:name])
           build_attrs.slice!(:name,
                              :commands,
                              :tag_list,
@@ -26,17 +31,21 @@ module Ci
                              :stage,
                              :stage_idx)
 
-          build_attrs.merge!(ref: ref,
-                             tag: tag,
+          build_attrs.merge!(ref: @commit.ref,
+                             tag: @commit.tag,
                              trigger_request: trigger_request,
                              user: user,
-                             project: commit.project)
+                             project: @commit.project)
 
-          build = commit.builds.create!(build_attrs)
-          build.execute_hooks
-          build
+          @commit.builds.create!(build_attrs)
         end
       end
     end
+
+    private
+
+    def config_processor
+      @config_processor ||= @commit.config_processor
+    end
   end
 end
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index b3dfc707221b54e569042faabf5743eedbf7d29f..d3745c770eaa49a40a954abeb108724c0b8f6750 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -7,7 +7,7 @@ module Ci
       # check if ref is tag
       tag = project.repository.find_tag(ref).present?
 
-      ci_commit = project.ensure_ci_commit(commit.sha)
+      ci_commit = project.ci_commits.create(commit.sha, ref)
 
       trigger_request = trigger.trigger_requests.create!(
         variables: variables,
diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb
index 69d5c42a87758dd1a791f374b6592b2a87017ccd..e7e1134ce4bc1a18ebddfdd290c4cfdd012d5c90 100644
--- a/app/services/create_commit_builds_service.rb
+++ b/app/services/create_commit_builds_service.rb
@@ -2,6 +2,7 @@ class CreateCommitBuildsService
   def execute(project, user, params)
     return false unless project.builds_enabled?
 
+    before_sha = params[:checkout_sha] || params[:before]
     sha = params[:checkout_sha] || params[:after]
     origin_ref = params[:ref]
 
@@ -10,15 +11,16 @@ class CreateCommitBuildsService
     end
 
     ref = Gitlab::Git.ref_name(origin_ref)
+    tag = Gitlab::Git.tag_ref?(origin_ref)
 
     # Skip branch removal
     if sha == Gitlab::Git::BLANK_SHA
       return false
     end
 
-    commit = project.ci_commit(sha)
+    commit = project.ci_commit(sha, ref)
     unless commit
-      commit = project.ci_commits.new(sha: sha)
+      commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag)
 
       # Skip creating ci_commit when no gitlab-ci.yml is found
       unless commit.ci_yaml_file
@@ -32,8 +34,7 @@ class CreateCommitBuildsService
     # Skip creating builds for commits that have [ci skip]
     unless commit.skip_ci?
       # Create builds for commit
-      tag = Gitlab::Git.tag_ref?(origin_ref)
-      commit.create_builds(ref, tag, user)
+      commit.create_builds(user)
     end
 
     commit
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 8700b4820cd6b9d65f5eee6edb427dbf97e4f3f7..50d0005140926f47a173fdba7b9cf9214ae5aa4a 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -117,7 +117,7 @@
 
           %td.build-link
             - if project
-              = link_to ci_status_path(build.commit) do
+              = link_to builds_namespace_project_commit_path(project.namespace, project, build.sha) do
                 %strong #{build.commit.short_sha}
 
           %td.timestamp
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
index 386d72e77872333baab57b66ac26386bb24bcbd1..6eccbaa4bfa5edbf9269d4f15385a5216f4c4568 100644
--- a/app/views/projects/_last_commit.html.haml
+++ b/app/views/projects/_last_commit.html.haml
@@ -1,9 +1,8 @@
 .project-last-commit
-  - ci_commit = project.ci_commit(commit.sha)
-  - if ci_commit
-    = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
-      = ci_status_icon(ci_commit)
-      = ci_status_label(ci_commit)
+  - if commit.status
+    = link_to project_commit_path(commit.project, commit), class: "ci-status ci-#{commit.status}" do
+      = ci_icon_for_status(commit.status)
+      = ci_label_for_status(commit.status)
 
   = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
   = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index b02aee3db212e730bccf6605778799c326c15228..41b1ca9f9e8443edf298c449f471c18820daace7 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -4,7 +4,7 @@
 .build-page
   .gray-content-block.top-block
     Build ##{@build.id} for commit
-    %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
+    %strong.monospace= link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha)
     from
     = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
     - merge_request = @build.merge_request
@@ -13,7 +13,7 @@
       = link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request)
 
   #up-build-trace
-  - builds = @build.commit.matrix_builds(@build)
+  - builds = @build.commit.builds.latest.to_a
   - if builds.size > 1
     %ul.nav-links.no-top.no-bottom
       - builds.each do |build|
@@ -173,7 +173,7 @@
           Commit
           .pull-right
             %small
-              = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
+              = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha), class: "monospace"
         %p
           %span.attr-name Branch:
           = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
@@ -196,7 +196,7 @@
         .build-widget
           %h4.title #{pluralize(@builds.count(:id), "other build")} for
           = succeed ":" do
-            = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
+            = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, build.sha), class: "monospace"
           %table.table.builds
             - @builds.each_with_index do |build, i|
               %tr.build
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 2cf9115e4ddec0ab079a01896e2983051ecc4e19..218d396b898470d2e9642d18c1a07c1a028f22f5 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -19,11 +19,12 @@
     %td
       = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
 
-  %td
-    - if build.ref
-      = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
-    - else
-      .light none
+  - if !defined?(ref) || ref
+    %td
+      - if build.ref
+        = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
+      - else
+        .light none
 
   - if defined?(runner) && runner
     %td
@@ -48,6 +49,8 @@
         %span.label.label-info triggered
       - if build.try(:allow_failure)
         %span.label.label-danger allowed to fail
+      - if defined?(retried) && retried
+        %span.label.label-warning retried
 
   %td.duration
     - if build.duration
diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/app/views/projects/ci_commits/_header_title.html.haml b/app/views/projects/ci_commits/_header_title.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/app/views/projects/ci_commits/index.html.haml b/app/views/projects/ci_commits/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/app/views/projects/ci_commits/new.html.haml b/app/views/projects/ci_commits/new.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml
index 003b7c18d0e699230454ffed82dfa300af6cd0f4..5c9a319edebeb0c4b4d1da5165549e7bf76d10da 100644
--- a/app/views/projects/commit/_builds.html.haml
+++ b/app/views/projects/commit/_builds.html.haml
@@ -1,67 +1,2 @@
-.gray-content-block.middle-block
-  .pull-right
-    - if can?(current_user, :update_build, @ci_commit.project)
-      - if @ci_commit.builds.latest.failed.any?(&:retryable?)
-        = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
-
-      - if @ci_commit.builds.running_or_pending.any?
-        = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
-
-  .oneline
-    = pluralize @statuses.count(:id), "build"
-    - if defined?(link_to_commit) && link_to_commit
-      for commit
-      = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: "monospace"
-    - if @ci_commit.duration > 0
-      in
-      = time_interval_in_words @ci_commit.duration
-
-- if @ci_commit.yaml_errors.present?
-  .bs-callout.bs-callout-danger
-    %h4 Found errors in your .gitlab-ci.yml:
-    %ul
-      - @ci_commit.yaml_errors.split(",").each do |error|
-        %li= error
-    You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
-
-- if @ci_commit.project.builds_enabled? && !@ci_commit.ci_yaml_file
-  .bs-callout.bs-callout-warning
-    \.gitlab-ci.yml not found in this commit
-
-.table-holder
-  %table.table.builds
-    %thead
-      %tr
-        %th Status
-        %th Build ID
-        %th Ref
-        %th Stage
-        %th Name
-        %th Duration
-        %th Finished at
-        - if @ci_commit.project.build_coverage_enabled?
-          %th Coverage
-        %th
-    - @ci_commit.refs.each do |ref|
-      - builds = @ci_commit.statuses.for_ref(ref).latest.ordered
-      = render builds, coverage: @ci_commit.project.build_coverage_enabled?, stage: true, allow_retry: true
-
-- if @ci_commit.retried.any?
-  .gray-content-block.second-block
-    Retried builds
-
-  .table-holder
-    %table.table.builds
-      %thead
-        %tr
-          %th Status
-          %th Build ID
-          %th Ref
-          %th Stage
-          %th Name
-          %th Duration
-          %th Finished at
-          - if @ci_commit.project.build_coverage_enabled?
-            %th Coverage
-          %th
-      = render @ci_commit.retried, coverage: @ci_commit.project.build_coverage_enabled?, stage: true
+- @ci_commits.each do |ci_commit|
+  = render "ci_commit", ci_commit: ci_commit
diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..06520e40bd96170e0fcf7cba70972a9832d15322
--- /dev/null
+++ b/app/views/projects/commit/_ci_commit.html.haml
@@ -0,0 +1,69 @@
+.gray-content-block.middle-block
+  .pull-right
+    - if can?(current_user, :update_build, @project)
+      - if ci_commit.builds.latest.failed.any?(&:retryable?)
+        = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: 'btn btn-grouped btn-primary', method: :post
+
+      - if ci_commit.builds.running_or_pending.any?
+        = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
+
+  .oneline
+    = pluralize ci_commit.statuses.count(:id), "build"
+    - if ci_commit.ref
+      for
+      %span.label.label-info
+        = ci_commit.ref
+    - if defined?(link_to_commit) && link_to_commit
+      for commit
+      = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace"
+    - if ci_commit.duration > 0
+      in
+      = time_interval_in_words ci_commit.duration
+
+- if ci_commit.yaml_errors.present?
+  .bs-callout.bs-callout-danger
+    %h4 Found errors in your .gitlab-ci.yml:
+    %ul
+      - ci_commit.yaml_errors.split(",").each do |error|
+        %li= error
+    You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
+
+- if @project.builds_enabled? && !ci_commit.ci_yaml_file
+  .bs-callout.bs-callout-warning
+    \.gitlab-ci.yml not found in this commit
+
+.table-holder
+  %table.table.builds
+    %thead
+      %tr
+        %th Status
+        %th Build ID
+        %th Stage
+        %th Name
+        %th Duration
+        %th Finished at
+        - if @project.build_coverage_enabled?
+          %th Coverage
+        %th
+    - builds = ci_commit.statuses.latest.ordered
+    = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true
+
+- if ci_commit.retried.any?
+  .gray-content-block.second-block
+    Retried builds
+
+  .table-holder
+    %table.table.builds
+      %thead
+        %tr
+          %th Status
+          %th Build ID
+          %th Ref
+          %th Stage
+          %th Name
+          %th Duration
+          %th Finished at
+          - if @project.build_coverage_enabled?
+            %th Coverage
+          %th
+      = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 71995fcc487aea3db796952e4ff299880e2444d4..0908e830f83b135e6e6f0233d9e18ab2cd7e1e9e 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -42,12 +42,12 @@
   - @commit.parents.each do |parent|
     = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace"
 
-- if @ci_commit
+- if @commit.status
   .pull-right
-    = link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
-      = ci_status_icon(@ci_commit)
+    = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do
+      = ci_icon_for_status(@commit.status)
       build:
-      = ci_status_label(@ci_commit)
+      = ci_label_for_status(@commit.status)
 
 .commit-info-row.branches
   %i.fa.fa-spinner.fa-spin
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 21e186120c375666f65a9be3ff97f36f9006336f..096f7058bd43a145f8b1541e52fe69b4eb29eeaa 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -5,7 +5,7 @@
 
 .prepend-top-default
   = render "commit_box"
-- if @ci_commit
+- if @commit.status
   = render "ci_menu"
 - else
   %div.block-connector
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 7f2903589a90dd501053623795d26d2625676958..fa34f7b7d6112b8f43da0c05b4fdf03c28456483 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -4,9 +4,8 @@
   - notes = commit.notes
   - note_count = notes.user.count
 
-- ci_commit = project.ci_commit(commit.sha)
 - cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
-- cache_key.push(ci_commit.status) if ci_commit
+- cache_key.push(commit.status) if commit.status
 
 = cache(cache_key) do
   %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
@@ -17,8 +16,8 @@
           %a.text-expander.js-toggle-button ...
 
       .pull-right
-        - if ci_commit
-          = render_ci_status(ci_commit)
+        - if commit.status
+          = render_ci_status(commit)
           &nbsp;
         = clipboard_button(clipboard_text: commit.id)
         = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index b10cd03515f28866dd98e0b732ef52f55f3abf96..bdfa0c7009eecab628777abcccc44cd67bc34cbe 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -5,7 +5,7 @@
     - @related_branches.each do |branch|
       %li
         - sha = @project.repository.find_branch(branch).target
-        - ci_commit = @project.ci_commit(sha) if sha
+        - ci_commit = @project.ci_commit(sha, branch) if sha
         - if ci_commit
           %span.related-branch-ci-status
             = render_ci_status(ci_commit)
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 53ff8959bc8c3ec2213148dc52e1850f92ed0f60..53261fcace75b90dfa374b1214469864d9a1563e 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -6,9 +6,8 @@
 - css_class = '' unless local_assigns[:css_class]
 - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit
 - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
-- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit
 - cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3']
-- cache_key.push(ci_commit.status) if ci_commit
+- cache_key.push(project.commit.status) if project.commit.status
 
 %li.project-row{ class: css_class }
   = cache(cache_key) do
@@ -16,9 +15,9 @@
       - if project.main_language
         %span
           = project.main_language
-      - if ci_commit
+      - if project.commit.status
         %span
-          = render_ci_status(ci_commit)
+          = render_ci_status(project.commit)
       - if forks
         %span
           = icon('code-fork')
diff --git a/db/migrate/20160331153918_add_fields_to_ci_commit.rb b/db/migrate/20160331153918_add_fields_to_ci_commit.rb
new file mode 100644
index 0000000000000000000000000000000000000000..03eb9ba4e5313a8584921282b9206e4f36d22775
--- /dev/null
+++ b/db/migrate/20160331153918_add_fields_to_ci_commit.rb
@@ -0,0 +1,7 @@
+class AddFieldsToCiCommit < ActiveRecord::Migration
+  def change
+    add_column :ci_commits, :status, :string
+    add_column :ci_commits, :started_at, :timestamp
+    add_column :ci_commits, :finished_at, :timestamp
+  end
+end
diff --git a/db/migrate/20160331204039_add_action_to_ci_commit.rb b/db/migrate/20160331204039_add_action_to_ci_commit.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e9f8eb624d6f8547d1362ed4fd99231d6e31ae1a
--- /dev/null
+++ b/db/migrate/20160331204039_add_action_to_ci_commit.rb
@@ -0,0 +1,5 @@
+class AddActionToCiCommit < ActiveRecord::Migration
+  def change
+    add_column :ci_commits, :action, :string
+  end
+end
diff --git a/db/migrate/20160411122626_add_duration_to_ci_commit.rb b/db/migrate/20160411122626_add_duration_to_ci_commit.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7def7a48cde54e26d5567625b4128f958cd66bc1
--- /dev/null
+++ b/db/migrate/20160411122626_add_duration_to_ci_commit.rb
@@ -0,0 +1,5 @@
+class AddDurationToCiCommit < ActiveRecord::Migration
+  def change
+    add_column :ci_commits, :duration, :integer
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index ec235c19131645d54a26a40d451599b2ace8130a..72d63913387f69f6154fefc741b7ba344e8510e0 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20160331223143) do
+ActiveRecord::Schema.define(version: 20160411122626) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -141,6 +141,7 @@ ActiveRecord::Schema.define(version: 20160331223143) do
     t.text     "artifacts_metadata"
     t.integer  "erased_by_id"
     t.datetime "erased_at"
+    t.string   "plugin"
   end
 
   add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
@@ -168,6 +169,11 @@ ActiveRecord::Schema.define(version: 20160331223143) do
     t.text     "yaml_errors"
     t.datetime "committed_at"
     t.integer  "gl_project_id"
+    t.string   "status"
+    t.datetime "started_at"
+    t.datetime "finished_at"
+    t.string   "action"
+    t.integer  "duration"
   end
 
   add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree
@@ -306,11 +312,20 @@ ActiveRecord::Schema.define(version: 20160331223143) do
   add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree
 
   create_table "ci_trigger_requests", force: :cascade do |t|
-    t.integer  "trigger_id", null: false
+    t.integer  "trigger_id"
     t.text     "variables"
     t.datetime "created_at"
     t.datetime "updated_at"
     t.integer  "commit_id"
+    t.string   "sha"
+    t.string   "before_sha"
+    t.string   "ref"
+    t.string   "action"
+    t.string   "status"
+    t.datetime "started_at"
+    t.datetime "finished_at"
+    t.integer  "project_id"
+    t.string   "origin_ref"
   end
 
   create_table "ci_triggers", force: :cascade do |t|
@@ -731,6 +746,7 @@ ActiveRecord::Schema.define(version: 20160331223143) do
     t.boolean  "public_builds",          default: true,     null: false
     t.string   "main_language"
     t.integer  "pushes_since_gc",        default: 0
+    t.boolean  "images_enabled"
   end
 
   add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 8e74e177ea05c5f66b10871508d765d915b34f1e..e7d76764ff58bdf6b1926536ce72d3b36db1afa7 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -21,7 +21,7 @@ module API
         authorize!(:read_commit_status, user_project)
 
         not_found!('Commit') unless user_project.commit(params[:sha])
-        ci_commit = user_project.ci_commit(params[:sha])
+        ci_commit = user_project.ci_commit(params[:sha], params[:ref])
         return [] unless ci_commit
 
         statuses = ci_commit.statuses