From 546a3c6561fbe967cc37ccc3229b71893cd20c34 Mon Sep 17 00:00:00 2001
From: Kamil Trzcinski <ayufan@ayufan.eu>
Date: Fri, 2 Oct 2015 13:46:38 +0200
Subject: [PATCH] Refactor commit and build

---
 app/controllers/ci/builds_controller.rb       |   2 +-
 app/controllers/ci/commits_controller.rb      |   6 +-
 app/controllers/ci/projects_controller.rb     |   3 +-
 app/helpers/ci/commits_helper.rb              |   2 +-
 app/helpers/ci/gitlab_helper.rb               |   2 +-
 app/helpers/ci_status_helper.rb               |   2 +-
 app/models/ci/build.rb                        |  16 ++
 app/models/ci/commit.rb                       | 137 ++++++------------
 app/models/ci/project_status.rb               |  12 --
 app/models/project.rb                         |   6 +-
 .../project_services/ci/hip_chat_message.rb   |   2 +-
 .../project_services/ci/mail_service.rb       |   2 +-
 .../project_services/ci/slack_message.rb      |   2 +-
 .../project_services/gitlab_ci_service.rb     |   4 +-
 app/services/ci/create_builds_service.rb      |  27 ++++
 app/services/ci/create_commit_service.rb      |  39 ++---
 .../ci/create_trigger_request_service.rb      |   6 +-
 app/views/ci/builds/_build.html.haml          |   4 +
 app/views/ci/builds/show.html.haml            |   6 +-
 app/views/ci/commits/_commit.html.haml        |   4 +-
 app/views/ci/commits/show.html.haml           |  57 ++++----
 .../ci/notify/build_fail_email.html.haml      |   2 +-
 app/views/ci/notify/build_fail_email.text.erb |   2 +-
 .../ci/notify/build_success_email.html.haml   |   2 +-
 .../ci/notify/build_success_email.text.erb    |   2 +-
 config/routes.rb                              |  10 +-
 .../20151002112914_add_stage_idx_to_builds.rb |   5 +
 ...20151002121400_add_index_for_build_name.rb |   5 +
 ...0151002122929_add_sha_and_ref_to_builds.rb |   7 +
 ...1002122943_migrate_sha_and_ref_to_build.rb |   7 +
 lib/ci/gitlab_ci_yaml_processor.rb            |   1 +
 spec/requests/ci/commits_spec.rb              |   2 +-
 32 files changed, 193 insertions(+), 193 deletions(-)
 create mode 100644 app/services/ci/create_builds_service.rb
 create mode 100644 db/migrate/20151002112914_add_stage_idx_to_builds.rb
 create mode 100644 db/migrate/20151002121400_add_index_for_build_name.rb
 create mode 100644 db/migrate/20151002122929_add_sha_and_ref_to_builds.rb
 create mode 100644 db/migrate/20151002122943_migrate_sha_and_ref_to_build.rb

diff --git a/app/controllers/ci/builds_controller.rb b/app/controllers/ci/builds_controller.rb
index 80ee866679..bf87f81439 100644
--- a/app/controllers/ci/builds_controller.rb
+++ b/app/controllers/ci/builds_controller.rb
@@ -18,7 +18,7 @@ module Ci
 
         if commit
           # Redirect to commit page
-          redirect_to ci_project_ref_commit_path(@project, @build.commit.ref, @build.commit.sha)
+          redirect_to ci_project_commit_path(@project, @build.commit)
           return
         end
       end
diff --git a/app/controllers/ci/commits_controller.rb b/app/controllers/ci/commits_controller.rb
index 7a0a500fbe..acf9189572 100644
--- a/app/controllers/ci/commits_controller.rb
+++ b/app/controllers/ci/commits_controller.rb
@@ -13,7 +13,7 @@ module Ci
     end
 
     def status
-      commit = Ci::Project.find(params[:project_id]).commits.find_by_sha_and_ref!(params[:id], params[:ref_id])
+      commit = Ci::Project.find(params[:project_id]).commits.find_by_sha!(params[:id], params[:ref_id])
       render json: commit.to_json(only: [:id, :sha], methods: [:status, :coverage])
     rescue ActiveRecord::RecordNotFound
       render json: { status: "not_found" }
@@ -22,7 +22,7 @@ module Ci
     def cancel
       commit.builds.running_or_pending.each(&:cancel)
 
-      redirect_to ci_project_ref_commits_path(project, commit.ref, commit.sha)
+      redirect_to ci_project_commits_path(project, commit.sha)
     end
 
     private
@@ -32,7 +32,7 @@ module Ci
     end
 
     def commit
-      @commit ||= Ci::Project.find(params[:project_id]).commits.find_by_sha_and_ref!(params[:id], params[:ref_id])
+      @commit ||= Ci::Project.find(params[:project_id]).commits.find_by_sha!(params[:id])
     end
   end
 end
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index e8788955eb..20e6c2c2ba 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -19,7 +19,8 @@ module Ci
       @ref = params[:ref]
 
       @commits = @project.commits.reverse_order
-      @commits = @commits.where(ref: @ref) if @ref
+      # TODO: this is broken
+      # @commits = @commits.where(ref: @ref) if @ref
       @commits = @commits.page(params[:page]).per(20)
     end
 
diff --git a/app/helpers/ci/commits_helper.rb b/app/helpers/ci/commits_helper.rb
index 9069aed5b4..a0df4c3d72 100644
--- a/app/helpers/ci/commits_helper.rb
+++ b/app/helpers/ci/commits_helper.rb
@@ -1,7 +1,7 @@
 module Ci
   module CommitsHelper
     def ci_commit_path(commit)
-      ci_project_ref_commits_path(commit.project, commit.ref, commit.sha)
+      ci_project_commits_path(commit.project, commit)
     end
 
     def commit_link(commit)
diff --git a/app/helpers/ci/gitlab_helper.rb b/app/helpers/ci/gitlab_helper.rb
index 13e4d0fd9c..baddbc806f 100644
--- a/app/helpers/ci/gitlab_helper.rb
+++ b/app/helpers/ci/gitlab_helper.rb
@@ -26,7 +26,7 @@ module Ci
     def yaml_web_editor_link(project)
       commits = project.commits
 
-      if commits.any? && commits.last.push_data[:ci_yaml_file]
+      if commits.any? && commits.last.ci_yaml_file
         "#{project.gitlab_url}/edit/master/.gitlab-ci.yml"
       else
         "#{project.gitlab_url}/new/master"
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 3a88ed7107..794bdc2530 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -1,6 +1,6 @@
 module CiStatusHelper
   def ci_status_path(ci_commit)
-    ci_project_ref_commits_path(ci_commit.project, ci_commit.ref, ci_commit)
+    ci_project_commits_path(ci_commit.project, ci_commit)
   end
 
   def ci_status_icon(ci_commit)
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 645ad68e1b..bf3e891520 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -34,10 +34,12 @@ module Ci
     belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
 
     serialize :options
+    serialize :push_data
 
     validates :commit, presence: true
     validates :status, presence: true
     validates :coverage, numericality: true, allow_blank: true
+    validates_presence_of :ref
 
     scope :running, ->() { where(status: "running") }
     scope :pending, ->() { where(status: "pending") }
@@ -45,6 +47,9 @@ module Ci
     scope :failed, ->() { where(status: "failed")  }
     scope :unstarted, ->() { where(runner_id: nil) }
     scope :running_or_pending, ->() { where(status:[:running, :pending]) }
+    scope :latest, ->() { group(:name).order(stage_idx: :asc, created_at: :desc) }
+    scope :ignore_failures, ->() { where(allow_failure: false) }
+    scope :for_ref, ->(ref) { where(ref: ref) }
 
     acts_as_taggable
 
@@ -82,6 +87,7 @@ module Ci
         new_build.name = build.name
         new_build.allow_failure = build.allow_failure
         new_build.stage = build.stage
+        new_build.stage_idx = build.stage_idx
         new_build.trigger_request = build.trigger_request
         new_build.save
         new_build
@@ -187,6 +193,16 @@ module Ci
       project.name
     end
 
+    def project_recipients
+      recipients = project.email_recipients.split(' ')
+
+      if project.email_add_pusher? && push_data[:user_email].present?
+        recipients << push_data[:user_email]
+      end
+
+      recipients.uniq
+    end
+
     def repo_url
       project.repo_url_with_auth
     end
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index 6d048779cd..35134b6628 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -23,9 +23,7 @@ module Ci
     has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
     has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
 
-    serialize :push_data
-
-    validates_presence_of :ref, :sha, :before_sha, :push_data
+    validates_presence_of :sha
     validate :valid_commit_sha
 
     def self.truncate_sha(sha)
@@ -69,15 +67,15 @@ module Ci
     end
 
     def git_author_name
-      commit_data[:author][:name] if commit_data && commit_data[:author]
+      commit_data.author_name if commit_data
     end
 
     def git_author_email
-      commit_data[:author][:email] if commit_data && commit_data[:author]
+      commit_data.author_email if commit_data
     end
 
     def git_commit_message
-      commit_data[:message] if commit_data && commit_data[:message]
+      commit_data.message if commit_data
     end
 
     def short_before_sha
@@ -89,84 +87,31 @@ module Ci
     end
 
     def commit_data
-      push_data[:commits].find do |commit|
-        commit[:id] == sha
-      end
+      @commit ||= gl_project.commit(sha)
     rescue
       nil
     end
 
-    def project_recipients
-      recipients = project.email_recipients.split(' ')
-
-      if project.email_add_pusher? && push_data[:user_email].present?
-        recipients << push_data[:user_email]
-      end
-
-      recipients.uniq
-    end
-
     def stage
-      return unless config_processor
-      stages = builds_without_retry.select(&:active?).map(&:stage)
-      config_processor.stages.find { |stage| stages.include? stage }
+      builds_without_retry.group(:stage_idx).select(:stage).last
     end
 
-    def create_builds_for_stage(stage, trigger_request)
+    def create_builds(ref, tag, push_data, trigger_request = nil)
       return if skip_ci? && trigger_request.blank?
       return unless config_processor
-
-      builds_attrs = config_processor.builds_for_stage_and_ref(stage, ref, tag)
-      builds_attrs.map do |build_attrs|
-        builds.create!({
-                         name: build_attrs[:name],
-                         commands: build_attrs[:script],
-                         tag_list: build_attrs[:tags],
-                         options: build_attrs[:options],
-                         allow_failure: build_attrs[:allow_failure],
-                         stage: build_attrs[:stage],
-                         trigger_request: trigger_request,
-                       })
-      end
+      CreateBuildsService.new.execute(self, config_processor, ref, tag, push_data, trigger_request)
     end
 
-    def create_next_builds(trigger_request)
-      return if skip_ci? && trigger_request.blank?
-      return unless config_processor
-
-      stages = builds.where(trigger_request: trigger_request).group_by(&:stage)
-
-      config_processor.stages.any? do |stage|
-        !stages.include?(stage) && create_builds_for_stage(stage, trigger_request).present?
-      end
+    def refs
+      builds.group(:ref).pluck(:ref)
     end
 
-    def create_builds(trigger_request = nil)
-      return if skip_ci? && trigger_request.blank?
-      return unless config_processor
-
-      config_processor.stages.any? do |stage|
-        create_builds_for_stage(stage, trigger_request).present?
-      end
+    def last_ref
+      builds.latest.first.try(:ref)
     end
 
     def builds_without_retry
-      @builds_without_retry ||=
-        begin
-          grouped_builds = builds.group_by(&:name)
-          grouped_builds.map do |name, builds|
-            builds.sort_by(&:id).last
-          end
-        end
-    end
-
-    def builds_without_retry_sorted
-      return builds_without_retry unless config_processor
-
-      stages = config_processor.stages
-      builds_without_retry.sort_by do |build|
-        [stages.index(build.stage) || -1, build.name || ""]
-      end
+      builds.latest
     end
 
     def retried_builds
@@ -180,35 +125,32 @@ module Ci
         return 'failed'
       elsif builds.none?
         return 'skipped'
-      elsif success?
-        'success'
-      elsif pending?
-        'pending'
-      elsif running?
-        'running'
-      elsif canceled?
-        'canceled'
+      end
+
+      statuses = builds_without_retry.ignore_failures.pluck(:status)
+      if statuses.all? { |status| status == 'success' }
+        return 'success'
+      elsif statuses.all? { |status| status == 'pending' }
+        return 'pending'
+      elsif statuses.include?('running') || statuses.include?('pending')
+        return 'running'
+      elsif statuses.all? { |status| status == 'canceled' }
+        return 'canceled'
       else
-        'failed'
+        return 'failed'
       end
     end
 
     def pending?
-      builds_without_retry.all? do |build|
-        build.pending?
-      end
+      status == 'pending'
     end
 
     def running?
-      builds_without_retry.any? do |build|
-        build.running? || build.pending?
-      end
+      status == 'running'
     end
 
     def success?
-      builds_without_retry.all? do |build|
-        build.success? || build.ignored?
-      end
+      status == 'success'
     end
 
     def failed?
@@ -216,15 +158,17 @@ module Ci
     end
 
     def canceled?
-      builds_without_retry.all? do |build|
-        build.canceled?
-      end
+      status == 'canceled'
     end
 
     def duration
       @duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i
     end
 
+    def duration_for_ref(ref)
+      builds_without_retry.for_ref(ref).select(&:duration).sum(&:duration).to_i
+    end
+
     def finished_at
       @finished_at ||= builds.order('finished_at DESC').first.try(:finished_at)
     end
@@ -238,12 +182,12 @@ module Ci
       end
     end
 
-    def matrix?
-      builds_without_retry.size > 1
+    def matrix_for_ref?(ref)
+      builds_without_retry.for_ref(ref).pluck(:id).size > 1
     end
 
     def config_processor
-      @config_processor ||= Ci::GitlabCiYamlProcessor.new(push_data[:ci_yaml_file])
+      @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file)
     rescue Ci::GitlabCiYamlProcessor::ValidationError => e
       save_yaml_error(e.message)
       nil
@@ -253,10 +197,15 @@ module Ci
       nil
     end
 
+    def ci_yaml_file
+      gl_project.repository.blob_at(sha, '.gitlab-ci.yml')
+    rescue
+      nil
+    end
+
     def skip_ci?
       return false if builds.any?
-      commits = push_data[:commits]
-      commits.present? && commits.last[:message] =~ /(\[ci skip\])/
+      git_commit_message =~ /(\[ci skip\])/ if git_commit_message
     end
 
     def update_committed!
diff --git a/app/models/ci/project_status.rb b/app/models/ci/project_status.rb
index 6d5cafe81a..b66f1212f2 100644
--- a/app/models/ci/project_status.rb
+++ b/app/models/ci/project_status.rb
@@ -28,18 +28,6 @@ module Ci
       status
     end
 
-    # only check for toggling build status within same ref.
-    def last_commit_changed_status?
-      ref = last_commit.ref
-      last_commits = commits.where(ref: ref).last(2)
-
-      if last_commits.size < 2
-        false
-      else
-        last_commits[0].status != last_commits[1].status
-      end
-    end
-
     def last_commit_for_ref(ref)
       commits.where(ref: ref).last
     end
diff --git a/app/models/project.rb b/app/models/project.rb
index b90a82da9f..bb47b9abb0 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -744,7 +744,11 @@ class Project < ActiveRecord::Base
   end
 
   def ci_commit(sha)
-    gitlab_ci_project.commits.find_by(sha: sha) if gitlab_ci?
+    ci_commits.find_by(sha: sha)
+  end
+
+  def ensure_ci_commit(sha)
+    ci_commit(sha) || ci_commits.create(sha: sha)
   end
 
   def ensure_gitlab_ci_project
diff --git a/app/models/project_services/ci/hip_chat_message.rb b/app/models/project_services/ci/hip_chat_message.rb
index 25c72033ea..73fdbe801c 100644
--- a/app/models/project_services/ci/hip_chat_message.rb
+++ b/app/models/project_services/ci/hip_chat_message.rb
@@ -13,7 +13,7 @@ module Ci
       lines.push("<a href=\"#{ci_project_url(project)}\">#{project.name}</a> - ")
       
       if commit.matrix?
-        lines.push("<a href=\"#{ci_project_ref_commits_url(project, commit.ref, commit.sha)}\">Commit ##{commit.id}</a></br>")
+        lines.push("<a href=\"#{ci_project_commits_url(project, commit.sha)}\">Commit ##{commit.id}</a></br>")
       else
         first_build = commit.builds_without_retry.first
         lines.push("<a href=\"#{ci_project_build_url(project, first_build)}\">Build '#{first_build.name}' ##{first_build.id}</a></br>")
diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb
index 1bd2f33612..11a2743f96 100644
--- a/app/models/project_services/ci/mail_service.rb
+++ b/app/models/project_services/ci/mail_service.rb
@@ -61,7 +61,7 @@ module Ci
     end
 
     def execute(build)
-      build.commit.project_recipients.each do |recipient|
+      build.project_recipients.each do |recipient|
         case build.status.to_sym
         when :success
           mailer.build_success_email(build.id, recipient)
diff --git a/app/models/project_services/ci/slack_message.rb b/app/models/project_services/ci/slack_message.rb
index 757b196114..7d254836fb 100644
--- a/app/models/project_services/ci/slack_message.rb
+++ b/app/models/project_services/ci/slack_message.rb
@@ -48,7 +48,7 @@ module Ci
     def attachment_message
       out = "<#{ci_project_url(project)}|#{project_name}>: "
       if commit.matrix?
-        out << "Commit <#{ci_project_ref_commits_url(project, commit.ref, commit.sha)}|\##{commit.id}> "
+        out << "Commit <#{ci_project_commits_url(project, commit.sha)}|\##{commit.id}> "
       else
         build = commit.builds_without_retry.first
         out << "Build <#{ci_project_build_url(project, build)}|\##{build.id}> "
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 6d2cf79b69..fd10851653 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -63,7 +63,7 @@ class GitlabCiService < CiService
   end
 
   def get_ci_commit(sha, ref)
-    Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha_and_ref!(sha, ref)
+    Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha!(sha)
   end
 
   def commit_status(sha, ref)
@@ -80,7 +80,7 @@ class GitlabCiService < CiService
 
   def build_page(sha, ref)
     if project.gitlab_ci_project.present?
-      ci_project_ref_commits_url(project.gitlab_ci_project, ref, sha)
+      ci_project_commits_url(project.gitlab_ci_project, sha)
     end
   end
 
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
new file mode 100644
index 0000000000..e9c85410e5
--- /dev/null
+++ b/app/services/ci/create_builds_service.rb
@@ -0,0 +1,27 @@
+module Ci
+  class CreateBuildsService
+    def execute(commit, ref, tag, push_data, config_processor, trigger_request)
+      config_processor.stages.any? do |stage|
+        builds_attrs = config_processor.builds_for_stage_and_ref(stage, ref, tag)
+        builds_attrs.map do |build_attrs|
+          # don't create the same build twice
+          unless commit.builds.find_by_name_and_trigger_request(name: build_attrs[:name], ref: ref, tag: tag, trigger_request: trigger_request)
+            commit.builds.create!({
+                             name: build_attrs[:name],
+                             commands: build_attrs[:script],
+                             tag_list: build_attrs[:tags],
+                             options: build_attrs[:options],
+                             allow_failure: build_attrs[:allow_failure],
+                             stage: build_attrs[:stage],
+                             stage_idx: build_attrs[:stage_idx],
+                             trigger_request: trigger_request,
+                             ref: ref,
+                             tag: tag,
+                             push_data: push_data,
+                           })
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/app/services/ci/create_commit_service.rb b/app/services/ci/create_commit_service.rb
index 0a1abf89a9..9120a82edc 100644
--- a/app/services/ci/create_commit_service.rb
+++ b/app/services/ci/create_commit_service.rb
@@ -16,33 +16,22 @@ module Ci
         return false
       end
 
-      commit = project.commits.find_by_sha_and_ref(sha, ref)
-
-      # Create commit if not exists yet
-      unless commit
-        data = {
-          ref: ref,
-          sha: sha,
-          tag: origin_ref.start_with?('refs/tags/'),
-          before_sha: before_sha,
-          push_data: {
-            before: before_sha,
-            after: sha,
-            ref: ref,
-            user_name: params[:user_name],
-            user_email: params[:user_email],
-            repository: params[:repository],
-            commits: params[:commits],
-            total_commits_count: params[:total_commits_count],
-            ci_yaml_file: params[:ci_yaml_file]
-          }
-        }
-
-        commit = project.commits.create(data)
-      end
+      tag = origin_ref.start_with?('refs/tags/')
+      push_data = {
+        before: before_sha,
+        after: sha,
+        ref: ref,
+        user_name: params[:user_name],
+        user_email: params[:user_email],
+        repository: params[:repository],
+        commits: params[:commits],
+        total_commits_count: params[:total_commits_count],
+        ci_yaml_file: params[:ci_yaml_file]
+      }
 
+      commit = project.gl_project.ensure_ci_commit(sha)
       commit.update_committed!
-      commit.create_builds unless commit.builds.any?
+      commit.create_builds(ref, tag, push_data)
 
       commit
     end
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index 9bad09f2f5..f13ed787ed 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -1,15 +1,15 @@
 module Ci
   class CreateTriggerRequestService
     def execute(project, trigger, ref, variables = nil)
-      commit = project.commits.where(ref: ref).last
+      commit = project.gl_project.commit(ref)
       return unless commit
 
+      ci_commit = project.gl_project.ensure_ci_commit(commit.sha)
       trigger_request = trigger.trigger_requests.create!(
-        commit: commit,
         variables: variables
       )
 
-      if commit.create_builds(trigger_request)
+      if ci_commit.create_builds(ref, tag, nil, trigger_request)
         trigger_request
       end
     end
diff --git a/app/views/ci/builds/_build.html.haml b/app/views/ci/builds/_build.html.haml
index 515b862e99..8ccc0dff2f 100644
--- a/app/views/ci/builds/_build.html.haml
+++ b/app/views/ci/builds/_build.html.haml
@@ -6,6 +6,10 @@
     = link_to ci_project_build_path(build.project, build) do
       %strong Build ##{build.id}
 
+  - if defined?(ref)
+    %td
+      = build.ref
+
   %td
     = build.stage
 
diff --git a/app/views/ci/builds/show.html.haml b/app/views/ci/builds/show.html.haml
index 839dbf5c55..f941143e2e 100644
--- a/app/views/ci/builds/show.html.haml
+++ b/app/views/ci/builds/show.html.haml
@@ -1,7 +1,7 @@
 #up-build-trace
-- if @commit.matrix?
+- if @commit.matrix_for_ref?(@build.ref)
   %ul.center-top-menu
-    - @commit.builds_without_retry_sorted.each do |build|
+    - @commit.builds_without_retry.for_ref(build.ref).each do |build|
       %li{class: ('active' if build == @build) }
         = link_to ci_project_build_url(@project, build) do
           = ci_icon_for_status(build.status)
@@ -12,7 +12,7 @@
               = build.id
 
 
-    - unless @commit.builds_without_retry.include?(@build)
+    - unless @commit.builds_without_retry.for_ref(@build.ref).include?(@build)
       %li.active
         %a
           Build ##{@build.id}
diff --git a/app/views/ci/commits/_commit.html.haml b/app/views/ci/commits/_commit.html.haml
index 1eacfca944..f8a1fa5085 100644
--- a/app/views/ci/commits/_commit.html.haml
+++ b/app/views/ci/commits/_commit.html.haml
@@ -7,7 +7,7 @@
 
 
   %td.build-link
-    = link_to ci_project_ref_commits_path(commit.project, commit.ref, commit.sha) do
+    = link_to ci_project_commits_path(commit.project, commit.sha) do
       %strong #{commit.short_sha}
 
   %td.build-message
@@ -16,7 +16,7 @@
   %td.build-branch
     - unless @ref
       %span
-        = link_to truncate(commit.ref, length: 25), ci_project_path(@project, ref: commit.ref)
+        = link_to truncate(commit.last_ref, length: 25), ci_project_path(@project, ref: commit.last_ref)
 
   %td.duration
     - if commit.duration > 0
diff --git a/app/views/ci/commits/show.html.haml b/app/views/ci/commits/show.html.haml
index 8f38aa8467..4badb67128 100644
--- a/app/views/ci/commits/show.html.haml
+++ b/app/views/ci/commits/show.html.haml
@@ -17,14 +17,11 @@
           %p
             %span.attr-name Commit:
             #{gitlab_commit_link(@project, @commit.sha)}
-
-        %p
-          %span.attr-name Branch:
-          #{gitlab_ref_link(@project, @commit.ref)}
       .col-sm-6
-        %p
-          %span.attr-name Author:
-          #{@commit.git_author_name} (#{@commit.git_author_email})
+        - if @commit.git_author_name || @commit.git_author_email
+          %p
+            %span.attr-name Author:
+            #{@commit.git_author_name} (#{@commit.git_author_email})
         - if @commit.created_at
           %p
             %span.attr-name Created at:
@@ -33,7 +30,7 @@
 - if current_user && can?(current_user, :manage_builds, gl_project)
   .pull-right
     - if @commit.builds.running_or_pending.any?
-      = link_to "Cancel", cancel_ci_project_ref_commits_path(@project, @commit.ref, @commit.sha), class: 'btn btn-sm btn-danger'
+      = link_to "Cancel", cancel_ci_project_commits_path(@project, @commit), class: 'btn btn-sm btn-danger'
 
 
 - if @commit.yaml_errors.present?
@@ -43,30 +40,31 @@
       - @commit.yaml_errors.split(",").each do |error|
         %li= error
 
-- unless @commit.push_data[:ci_yaml_file]
+- unless @commit.ci_yaml_file
   .bs-callout.bs-callout-warning
     \.gitlab-ci.yml not found in this commit
 
-%h3
-  Builds
-  - if @commit.duration > 0
-    %small.pull-right
-      %i.fa.fa-time
-      #{time_interval_in_words @commit.duration}
+- @commit.refs.each do |ref|
+  %h3
+    Builds for #{ref}
+    - if @commit.duration_for_ref(ref) > 0
+      %small.pull-right
+        %i.fa.fa-time
+        #{time_interval_in_words @commit.duration_for_ref(ref)}
 
-%table.table.builds
-  %thead
-    %tr
-      %th Status
-      %th Build ID
-      %th Stage
-      %th Name
-      %th Duration
-      %th Finished at
-      - if @project.coverage_enabled?
-        %th Coverage
-      %th
-  = render @commit.builds_without_retry_sorted, controls: true
+  %table.table.builds
+    %thead
+      %tr
+        %th Status
+        %th Build ID
+        %th Stage
+        %th Name
+        %th Duration
+        %th Finished at
+        - if @project.coverage_enabled?
+          %th Coverage
+        %th
+    = render @commit.builds_without_retry.for_ref(ref), controls: true
 
 - if @commit.retried_builds.any?
   %h3
@@ -77,6 +75,7 @@
       %tr
         %th Status
         %th Build ID
+        %th Ref
         %th Stage
         %th Name
         %th Duration
@@ -84,4 +83,4 @@
         - if @project.coverage_enabled?
           %th Coverage
         %th
-    = render @commit.retried_builds
+    = render @commit.retried_builds, ref: true
diff --git a/app/views/ci/notify/build_fail_email.html.haml b/app/views/ci/notify/build_fail_email.html.haml
index d818e8b675..4ebdfa1b6c 100644
--- a/app/views/ci/notify/build_fail_email.html.haml
+++ b/app/views/ci/notify/build_fail_email.html.haml
@@ -11,7 +11,7 @@
 %p
   Author: #{@build.commit.git_author_name}
 %p
-  Branch: #{@build.commit.ref}
+  Branch: #{@build.ref}
 %p
   Message: #{@build.commit.git_commit_message}
 
diff --git a/app/views/ci/notify/build_fail_email.text.erb b/app/views/ci/notify/build_fail_email.text.erb
index 1add215a1c..177827f9a3 100644
--- a/app/views/ci/notify/build_fail_email.text.erb
+++ b/app/views/ci/notify/build_fail_email.text.erb
@@ -3,7 +3,7 @@ Build failed for <%= @project.name %>
 Status:   <%= @build.status %>
 Commit:   <%= @build.commit.short_sha %>
 Author:   <%= @build.commit.git_author_name %>
-Branch:   <%= @build.commit.ref %>
+Branch:   <%= @build.ref %>
 Message:  <%= @build.commit.git_commit_message %>
 
 Url:      <%= ci_project_build_url(@build.project, @build) %>
diff --git a/app/views/ci/notify/build_success_email.html.haml b/app/views/ci/notify/build_success_email.html.haml
index a20dcaee24..7cc43300e8 100644
--- a/app/views/ci/notify/build_success_email.html.haml
+++ b/app/views/ci/notify/build_success_email.html.haml
@@ -12,7 +12,7 @@
 %p
   Author: #{@build.commit.git_author_name}
 %p
-  Branch: #{@build.commit.ref}
+  Branch: #{@build.ref}
 %p
   Message: #{@build.commit.git_commit_message}
 
diff --git a/app/views/ci/notify/build_success_email.text.erb b/app/views/ci/notify/build_success_email.text.erb
index 7ebd17e727..4d55c39b0f 100644
--- a/app/views/ci/notify/build_success_email.text.erb
+++ b/app/views/ci/notify/build_success_email.text.erb
@@ -3,7 +3,7 @@ Build successful for <%= @project.name %>
 Status:   <%= @build.status %>
 Commit:   <%= @build.commit.short_sha %>
 Author:   <%= @build.commit.git_author_name %>
-Branch:   <%= @build.commit.ref %>
+Branch:   <%= @build.ref %>
 Message:  <%= @build.commit.git_commit_message %>
 
 Url:      <%= ci_project_build_url(@build.project, @build) %>
diff --git a/config/routes.rb b/config/routes.rb
index 6d96d8801c..56087e489f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -30,12 +30,10 @@ Gitlab::Application.routes.draw do
 
       resource :charts, only: [:show]
 
-      resources :refs, constraints: { ref_id: /.*/ }, only: [] do
-        resources :commits, only: [:show] do
-          member do
-            get :status
-            get :cancel
-          end
+      resources :commits, only: [:show] do
+        member do
+          get :status
+          get :cancel
         end
       end
 
diff --git a/db/migrate/20151002112914_add_stage_idx_to_builds.rb b/db/migrate/20151002112914_add_stage_idx_to_builds.rb
new file mode 100644
index 0000000000..68a745ffef
--- /dev/null
+++ b/db/migrate/20151002112914_add_stage_idx_to_builds.rb
@@ -0,0 +1,5 @@
+class AddStageIdxToBuilds < ActiveRecord::Migration
+  def change
+    add_column :ci_builds, :stage_idx, :integer
+  end
+end
diff --git a/db/migrate/20151002121400_add_index_for_build_name.rb b/db/migrate/20151002121400_add_index_for_build_name.rb
new file mode 100644
index 0000000000..c6a81d7466
--- /dev/null
+++ b/db/migrate/20151002121400_add_index_for_build_name.rb
@@ -0,0 +1,5 @@
+class AddIndexForBuildName < ActiveRecord::Migration
+  def up
+    add_index :ci_builds, [:commit_id, :stage_idx, :created_at]
+  end
+end
diff --git a/db/migrate/20151002122929_add_sha_and_ref_to_builds.rb b/db/migrate/20151002122929_add_sha_and_ref_to_builds.rb
new file mode 100644
index 0000000000..fc367341f1
--- /dev/null
+++ b/db/migrate/20151002122929_add_sha_and_ref_to_builds.rb
@@ -0,0 +1,7 @@
+class AddShaAndRefToBuilds < ActiveRecord::Migration
+  def change
+    add_column :ci_builds, :tag, :boolean
+    add_column :ci_builds, :ref, :string
+    add_column :ci_builds, :push_data, :text
+  end
+end
diff --git a/db/migrate/20151002122943_migrate_sha_and_ref_to_build.rb b/db/migrate/20151002122943_migrate_sha_and_ref_to_build.rb
new file mode 100644
index 0000000000..b80808946d
--- /dev/null
+++ b/db/migrate/20151002122943_migrate_sha_and_ref_to_build.rb
@@ -0,0 +1,7 @@
+class MigrateShaAndRefToBuild < ActiveRecord::Migration
+  def change
+    execute('UPDATE ci_builds SET ref=(SELECT ref FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id) WHERE ref IS NULL')
+    execute('UPDATE ci_builds SET push_data=(SELECT push_data FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id) WHERE push_data IS NULL')
+    execute('UPDATE ci_builds SET tag=(SELECT tag FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id) WHERE tag IS NULL')
+  end
+end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index e625e790df..861da770d3 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -85,6 +85,7 @@ module Ci
 
     def build_job(name, job)
       {
+        stage_idx: stages.index(job[:stage]),
         stage: job[:stage],
         script: "#{@before_script.join("\n")}\n#{normalize_script(job[:script])}",
         tags: job[:tags] || [],
diff --git a/spec/requests/ci/commits_spec.rb b/spec/requests/ci/commits_spec.rb
index 3ab8c915df..f43a3982d7 100644
--- a/spec/requests/ci/commits_spec.rb
+++ b/spec/requests/ci/commits_spec.rb
@@ -7,7 +7,7 @@ describe "Commits" do
 
   describe "GET /:project/refs/:ref_name/commits/:id/status.json" do
     before do
-      get status_ci_project_ref_commits_path(@commit.project, @commit.ref, @commit.sha), format: :json
+      get status_ci_project_commits_path(@commit.project, @commit.sha), format: :json
     end
 
     it { expect(response.status).to eq(200) }
-- 
2.30.9