diff options
30 files changed, 718 insertions, 183 deletions
diff --git a/app/controllers/projects/ci_commits_controller.rb b/app/controllers/projects/ci_commits_controller.rb new file mode 100644 index 00000000000..e4797833b06 --- /dev/null +++ b/app/controllers/projects/ci_commits_controller.rb @@ -0,0 +1,43 @@ +class Projects::CiCommitsController < Projects::ApplicationController + before_action :ci_commit, except: [:index] + before_action :authorize_read_build! + layout 'project' + + def index + @scope = params[:scope] + @all_commits = project.ci_commits + @commits = @all_commits.order(id: :desc) + @commits = + case @scope + when 'latest' + @commits + when 'branches' + refs = project.repository.branches.map(&:name) + ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') + @commits.where(id: ids) + when 'tags' + refs = project.repository.tags.map(&:name) + ids = @all_commits.where(ref: refs).group(:ref).select('max(id)') + @commits.where(id: ids) + else + @commits + end + @commits = @commits.page(params[:page]).per(30) + end + + def show + @commit = @ci_commit.commit + @builds = @ci_commit.builds + @statuses = @ci_commit.statuses + + respond_to do |format| + format.html + end + end + + private + + def ci_commit + @ci_commit ||= project.ci_commits.find_by!(id: params[:id]) + end +end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 576fa3cedb2..ef20281e82f 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 = ci_commits.statuses + @builds = @statuses 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 49064f5d505..ed5a1901056 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/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f3fddef01cb..e4580eea524 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -25,6 +25,10 @@ module GitlabRoutingHelper namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) end + def project_ci_commits_path(project, *args) + namespace_project_ci_commits_path(project.namespace, project, *args) + end + def project_builds_path(project, *args) namespace_project_builds_path(project.namespace, project, *args) end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 7d33838044b..c99aeff6f1c 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -126,12 +126,12 @@ module Ci end def retried? - !self.commit.latest_statuses_for_ref(self.ref).include?(self) + !self.commit.latest.include?(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 f4cf7034b14..cd9ce0a283d 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -19,19 +19,54 @@ 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' has_many :builds, class_name: 'Ci::Build' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' + 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]) } + validates_presence_of :sha + validates_presence_of :before_sha + validates_presence_of :ref validate :valid_commit_sha + # Make sure that status is saved + before_save :status + + def ignored? + false + end + def self.truncate_sha(sha) sha[0...8] end + def self.retried + all.map { |commit| commit.retried }.flatten + end + + def self.statuses + CommitStatus.where(commit: all) + end + + def self.builds + Ci::Build.where(commit: all) + end + + def self.stages + CommitStatus.where(commit: all) + .group(:stage, :stage_idx).order(:stage_idx) + .pluck(:stage, :stage_idx).map(&:first) + end + def to_param sha end @@ -68,9 +103,16 @@ module Ci nil end - def stage - running_or_pending = statuses.latest.running_or_pending.ordered - running_or_pending.first.try(:stage) + def origin_ref + ref + end + + def tag? + Gitlab::Git::tag_ref?(origin_ref) + end + + def branch? + Gitlab::Git::branch_ref?(origin_ref) end def create_builds(ref, tag, user, trigger_request = nil) @@ -101,16 +143,8 @@ module Ci 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 } + def latest + statuses.latest end def matrix_builds(build = nil) @@ -124,52 +158,39 @@ module Ci end def status - if yaml_errors.present? - return 'failed' - end - - @status ||= Ci::Status.get_status(latest_statuses) - end - - def pending? - status == 'pending' + read_attribute(:status) || update_status end - def running? - status == 'running' - end - - def success? - status == 'success' - end - - def failed? - status == 'failed' - end - - def canceled? - status == 'canceled' + def duration + duration_array = latest.map(&:duration).compact + duration_array.reduce(:+).to_i end - def active? - running? || pending? + def started_at + read_attribute(:started_at) || update_started_at end - def complete? - canceled? || success? || failed? + def finished_at + read_attribute(:finished_at) || update_finished_at end - def duration - duration_array = statuses.map(&:duration).compact - duration_array.reduce(:+).to_i + def update_status + status = + if yaml_errors.present? + 'failed' + else + latest.status + end end - def started_at - @started_at ||= statuses.order('started_at ASC').first.try(:started_at) + def update_started_at + started_at = + statuses.order(:id).first.try(:started_at) end - def finished_at - @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at) + def update_finished_at + finished_at = + statuses.order(id: :desc).first.try(:finished_at) end def coverage diff --git a/app/models/commit.rb b/app/models/commit.rb index d09876a07d9..89331768159 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -10,6 +10,9 @@ class Commit attr_mentionable :safe_message, pipeline: :single_line participant :author, :committer, :notes + alias_attribute :sha, :id + alias_attribute :short_sha, :short_id + attr_accessor :project DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] @@ -209,12 +212,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 3377a85a55a..3a059f01d49 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,7 +42,6 @@ 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 @@ -86,32 +87,9 @@ class CommitStatus < ActiveRecord::Base after_transition [:pending, :running] => :success do |commit_status| 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? - 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 00000000000..494db025048 --- /dev/null +++ b/app/models/concerns/ci_status.rb @@ -0,0 +1,50 @@ +module CiStatus + extend ActiveSupport::Concern + + module ClassMethods + def status + if all.none? + nil + elsif all.all? { |status| status.success? || status.ignored? } + 'success' + elsif all.all?(&:pending?) + 'pending' + elsif all.any?(&:running?) || all.any?(&:pending?) + 'running' + elsif all.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 + 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 bf185cb5dd8..33869e215c9 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 3e1f04b4158..b9d589a4594 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_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb index b3dfc707221..483cc0a72a3 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.ensure_ci_commit(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 69d5c42a877..e7e1134ce4b 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/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index d0f82b5f57f..862a3e087f1 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -24,6 +24,14 @@ Commits - if project_nav_tab? :builds + = nav_link(controller: %w(ci_commits)) do + = link_to project_ci_commits_path(@project), title: 'CI', class: 'shortcuts-ci' do + = icon('cubes fw') + %span + CI + %span.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count(:all)) + + - if project_nav_tab? :builds = nav_link(controller: %w(builds)) do = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do = icon('cubes fw') diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index 386d72e7787..47d9239eaa2 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 ci_status_path(commit), class: "ci-status ci-#{commit.status}" do + = ci_status_icon(commit) + = ci_status_label(commit) = 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/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 2cf9115e4dd..c109b6cdd20 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -1,4 +1,4 @@ -%tr.build +%tr.build{style:"text-decoration: #{if defined?(retried) && retried then "line-through" else "inherit" end}"} %td.status - if can?(current_user, :read_build, build) = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build)) @@ -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 00000000000..671c5abcbed --- /dev/null +++ b/app/views/projects/ci/commits/_commit.html.haml @@ -0,0 +1,141 @@ +%tr.commit + %td.commit-link + - if can?(current_user, :read_commit, commit) + = link_to namespace_project_commit_url(commit.project.namespace, commit.project, commit) do + %strong ##{commit.id} + - else + %strong ##{commit.id} + + %td.status + %div + - if can?(current_user, :read_commit, commit) + = ci_status_with_icon(commit.status, namespace_project_commit_url(commit.project.namespace, commit.project, commit)) + - else + = ci_status_with_icon(commit.status) + + %td + %div + - if commit.ref + = link_to commit.ref, namespace_project_commits_path(commit.project.namespace, commit.project, commit.ref) + + - if commit.tag? + %span.label.label-primary tag + - if commit.branch? + %span.label.label-primary branch + - if commit.trigger_requests.any? + %span.label.label-primary triggered + - if commit.yaml_errors.present? + %span.label.label-danger.has-tooltip(title="#{commit.yaml_errors}") yaml invalid + - if commit.builds.any?(&:stuck?) + %span.label.label-warning stuck + + - if commit_data = commit.commit_data + = render 'projects/branches/commit', commit: commit_data, project: @project + - else + %p + Cant find HEAD commit for this branch + + %td + %div + Duration: + + - if commit.started_at && commit.finished_at + #{duration_in_words(commit.finished_at, commit.started_at)} + - else + \- + %p + Finished: + + - if commit.finished_at + #{time_ago_with_tooltip(commit.finished_at)} + - else + \- + + %td.content + .controls.hidden-xs + = link_to project_builds_path(commit.project), class: 'btn btn-grouped btn-xs' do + = icon('cubes fw') + Details + + - @project = commit.project + - ref = 'master' + %span.btn-group.btn-grouped + %a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' } + %span.caret + %span.sr-only + Select Archive Format + %ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + %li + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do + %i.fa.fa-download + %span Download zip + %li + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar.gz + %li + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar.bz2 + %li + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar +-#%tr.commit +-# %td.status +-# - if can?(current_user, :read_commit, commit) +-# = ci_status_with_icon(commit.status, namespace_project_commit_url(commit.project.namespace, commit.project, commit)) +-# - else +-# = ci_status_with_icon(commit.status) +-# +-# %td.commit-link +-# - if can?(current_user, :read_commit, commit) +-# = link_to namespace_project_commit_url(commit.project.namespace, commit.project, commit) do +-# %strong ##{commit.id} +-# - else +-# %strong ##{commit.id} +-# +-# - if defined?(commit_sha) && commit_sha +-# %td +-# = link_to commit.short_sha, namespace_project_commit_path(commit.project.namespace, commit.project, commit.sha), class: "monospace" +-# +-# %td +-# - if commit.ref +-# = link_to commit.ref, namespace_project_commits_path(commit.project.namespace, commit.project, commit.ref) +-# - else +-# .light none +-# +-# %td +-# = commit.git_commit_message +-# +-# %td.duration +-# - if commit.started_at && commit.finished_at +-# #{duration_in_words(commit.finished_at, commit.started_at)} +-# +-# %td.timestamp +-# - if commit.finished_at +-# %span #{time_ago_with_tooltip(commit.finished_at)} +-# +-# - if defined?(coverage) && coverage +-# %td.coverage +-# - if commit.try(:coverage) +-# #{commit.coverage}% +-# +-# %td +-# .pull-right +-# - if can?(current_user, :read_commit, commit) && commit.artifacts? +-# = link_to download_namespace_project_commit_artifacts_path(commit.project.namespace, commit.project, commit), title: 'Download artifacts' do +-# %i.fa.fa-download +-# - if can?(current_user, :update_commit, commit) +-# - if commit.active? +-# = link_to cancel_namespace_project_commit_path(commit.project.namespace, commit.project, commit, return_to: request.original_url), method: :post, title: 'Cancel' do +-# %i.fa.fa-remove.cred +-# - elsif defined?(allow_retry) && allow_retry && commit.retryable? +-# = link_to retry_namespace_project_commit_path(commit.project.namespace, commit.project, commit, return_to: request.original_url), method: :post, title: 'Retry' do +-# %i.fa.fa-repeat +-# +-#- if commit.yaml_errors.present? +-# %tr +-# %td{colspan: 7} +-# .light +-# = commit.yaml_errors
\ No newline at end of file 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 00000000000..c04fc6be9f1 --- /dev/null +++ b/app/views/projects/ci_commits/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "CI", project_ci_commits_path(@project)) 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 00000000000..7955e5e9abf --- /dev/null +++ b/app/views/projects/ci_commits/index.html.haml @@ -0,0 +1,51 @@ +- page_title "CI Changes" += render "header_title" + +.top-area + %ul.nav-links + %li{class: ('active' if @scope.nil? || @scope == 'latest')} + = link_to project_ci_commits_path(@project, scope: :latest) do + Latest + %span.badge.js-totalbuilds-count + = number_with_delimiter(@all_commits.count(:id)) + + %li{class: ('active' if @scope == 'branches')} + = link_to project_ci_commits_path(@project, scope: :branches) do + Branches + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + %li{class: ('active' if @scope == 'tags')} + = link_to project_ci_commits_path(@project, scope: :tags) do + Tags + %span.badge.js-running-count + = number_with_delimiter(@all_commits.running_or_pending.count(:id)) + + %li{class: ('active' if @scope == 'failed')} + = link_to project_ci_commits_path(@project, scope: :failed) do + Failed + %span.badge.js-running-count + = number_with_delimiter(@all_commits.finished.count(:id)) + + .nav-controls + - if can?(current_user, :update_build, @project) + - unless @repository.gitlab_ci_yml + = link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info' + + = link_to ci_lint_path, class: 'btn btn-default' do + = icon('wrench') + %span CI Lint + +.gray-content-block + #{(@scope || 'running').capitalize} changes on this project + +%ul.content-list + - if @commits.blank? + %li + .nothing-here-block No commits to show + - else + .table-holder + %table.table.builds + = render @commits, commit_sha: true, stage: true, allow_retry: true + + = paginate @commits, theme: 'gitlab' diff --git a/app/views/projects/ci_commits/show.html.haml b/app/views/projects/ci_commits/show.html.haml new file mode 100644 index 00000000000..b02aee3db21 --- /dev/null +++ b/app/views/projects/ci_commits/show.html.haml @@ -0,0 +1,216 @@ +- page_title "#{@build.name} (##{@build.id})", "Builds" += render "header_title" + +.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) + from + = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) + - merge_request = @build.merge_request + - if merge_request + via + = link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request) + + #up-build-trace + - builds = @build.commit.matrix_builds(@build) + - if builds.size > 1 + %ul.nav-links.no-top.no-bottom + - builds.each do |build| + %li{class: ('active' if build == @build) } + = link_to namespace_project_build_path(@project.namespace, @project, build) do + = ci_icon_for_status(build.status) + %span + - if build.name + = build.name + - else + = build.id + + - if @build.retried? + %li.active + %a + Build ##{@build.id} + · + %i.fa.fa-warning + This build was retried. + + .gray-content-block.middle-block + .build-head + .clearfix + = ci_status_with_icon(@build.status) + - if @build.duration + %span + %i.fa.fa-time + #{duration_in_words(@build.finished_at, @build.started_at)} + .pull-right + #{time_ago_with_tooltip(@build.finished_at) if @build.finished_at} + + - if @build.stuck? + - unless @build.any_runners_online? + .bs-callout.bs-callout-warning + %p + - if no_runners_for_project?(@build.project) + This build is stuck, because the project doesn't have any runners online assigned to it. + - elsif @build.tags.any? + This build is stuck, because you don't have any active runners online with any of these tags assigned to them: + - @build.tags.each do |tag| + %span.label.label-primary + = tag + - else + This build is stuck, because you don't have any active runners that can run this build. + + %br + Go to + = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do + Runners page + + .row.prepend-top-default + .col-md-9 + .clearfix + - if @build.active? + .autoscroll-container + %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll + .clearfix + #js-build-scroll.scroll-controls + = link_to '#up-build-trace', class: 'btn' do + %i.fa.fa-angle-up + = link_to '#down-build-trace', class: 'btn' do + %i.fa.fa-angle-down + + - if @build.erased? + .erased.alert.alert-warning + - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by + Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)} + - else + %pre.trace#build-trace + %code.bash + = preserve do + = raw @build.trace_html + + %div#down-build-trace + + .col-md-3 + - if @build.coverage + .build-widget + %h4.title + Test coverage + %h1 #{@build.coverage}% + + - if can?(current_user, :read_build, @project) && @build.artifacts? + .build-widget.artifacts + %h4.title Build artifacts + .center + .btn-group{ role: :group } + = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary' do + = icon('download') + Download + + - if @build.artifacts_metadata? + = link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary' do + = icon('folder-open') + Browse + + .build-widget + %h4.title + Build ##{@build.id} + - if can?(current_user, :update_build, @project) + .center + .btn-group{ role: :group } + - if @build.active? + = link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-danger', method: :post + - elsif @build.retryable? + = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary', method: :post + + - if @build.erasable? + = link_to erase_namespace_project_build_path(@project.namespace, @project, @build), + class: 'btn btn-sm btn-warning', method: :post, + data: { confirm: 'Are you sure you want to erase this build?' } do + = icon('eraser') + Erase + + .clearfix + - if @build.duration + %p + %span.attr-name Duration: + #{duration_in_words(@build.finished_at, @build.started_at)} + %p + %span.attr-name Created: + #{time_ago_with_tooltip(@build.created_at)} + - if @build.finished_at + %p + %span.attr-name Finished: + #{time_ago_with_tooltip(@build.finished_at)} + - if @build.erased_at + %p + %span.attr-name Erased: + #{time_ago_with_tooltip(@build.erased_at)} + %p + %span.attr-name Runner: + - if @build.runner && current_user && current_user.admin + = link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id) + - elsif @build.runner + \##{@build.runner.id} + + - if @build.trigger_request + .build-widget + %h4.title + Trigger + + %p + %span.attr-name Token: + #{@build.trigger_request.trigger.short_token} + + - if @build.trigger_request.variables + %p + %span.attr-name Variables: + + %code + - @build.trigger_request.variables.each do |key, value| + #{key}=#{value} + + .build-widget + %h4.title + Commit + .pull-right + %small + = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" + %p + %span.attr-name Branch: + = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) + %p + %span.attr-name Author: + #{@build.commit.git_author_name} + %p + %span.attr-name Message: + #{@build.commit.git_commit_message} + + - if @build.tags.any? + .build-widget + %h4.title + Tags + - @build.tag_list.each do |tag| + %span.label.label-primary + = tag + + - if @builds.present? + .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" + %table.table.builds + - @builds.each_with_index do |build, i| + %tr.build + %td + = ci_icon_for_status(build.status) + %td + = link_to namespace_project_build_path(@project.namespace, @project, build) do + - if build.name + = build.name + - else + %span ##{build.id} + + %td.status= build.status + + + :javascript + new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}") diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml index 003b7c18d0e..5c9a319edeb 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 00000000000..06520e40bd9 --- /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 71995fcc487..f1da308b47b 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 ci_status_path(@commit), class: "ci-status ci-#{@commit.status}" do + = ci_status_icon(@commit) build: - = ci_status_label(@ci_commit) + = ci_status_label(@commit) .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 21e186120c3..096f7058bd4 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 7f2903589a9..fa34f7b7d61 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) = 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 b10cd03515f..bdfa0c7009e 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 53ff8959bc8..53261fcace7 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/config/routes.rb b/config/routes.rb index 842fbb99843..c55d9637299 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -654,6 +654,8 @@ Rails.application.routes.draw do resource :variables, only: [:show, :update] resources :triggers, only: [:index, :create, :destroy] + resources :ci_commits, only: [:index, :show] + resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do post :cancel_all 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 00000000000..03eb9ba4e53 --- /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/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 8e74e177ea0..e7d76764ff5 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 |