Commit 3b39214f authored by Alfredo Sumaran's avatar Alfredo Sumaran

Merge remote-tracking branch 'origin/master' into issue_14904

parents d4f6d398 1c7c114e
...@@ -4,6 +4,7 @@ v 8.8.0 (unreleased) ...@@ -4,6 +4,7 @@ v 8.8.0 (unreleased)
v 8.7.0 (unreleased) v 8.7.0 (unreleased)
- The number of InfluxDB points stored per UDP packet can now be configured - The number of InfluxDB points stored per UDP packet can now be configured
- Fix error when cross-project label reference used with non-existent project
- Transactions for /internal/allowed now have an "action" tag set - Transactions for /internal/allowed now have an "action" tag set
- Method instrumentation now uses Module#prepend instead of aliasing methods - Method instrumentation now uses Module#prepend instead of aliasing methods
- Repository.clean_old_archives is now instrumented - Repository.clean_old_archives is now instrumented
...@@ -50,6 +51,7 @@ v 8.7.0 (unreleased) ...@@ -50,6 +51,7 @@ v 8.7.0 (unreleased)
- Add endpoints to archive or unarchive a project !3372 - Add endpoints to archive or unarchive a project !3372
- Fix a bug whith trailing slash in bamboo_url - Fix a bug whith trailing slash in bamboo_url
- Add links to CI setup documentation from project settings and builds pages - Add links to CI setup documentation from project settings and builds pages
- Display project members page to all members
- Handle nil descriptions in Slack issue messages (Stan Hu) - Handle nil descriptions in Slack issue messages (Stan Hu)
- Add automated repository integrity checks - Add automated repository integrity checks
- API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling)
...@@ -68,6 +70,7 @@ v 8.7.0 (unreleased) ...@@ -68,6 +70,7 @@ v 8.7.0 (unreleased)
- Hide `Create a group` help block when creating a new project in a group - Hide `Create a group` help block when creating a new project in a group
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Allow issues and merge requests to be assigned to the author !2765 - Allow issues and merge requests to be assigned to the author !2765
- Make Ci::Commit to group only similar builds and make it stateful (ref, tag)
- Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Decouple membership and notifications - Decouple membership and notifications
- Fix creation of merge requests for orphaned branches (Stan Hu) - Fix creation of merge requests for orphaned branches (Stan Hu)
...@@ -87,10 +90,10 @@ v 8.7.0 (unreleased) ...@@ -87,10 +90,10 @@ v 8.7.0 (unreleased)
- Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu)
- Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld) - Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld)
- Improved markdown forms - Improved markdown forms
- Show JavaScript errors in sentry
- Diff design updates (colors, button styles, etc) - Diff design updates (colors, button styles, etc)
- Copying and pasting a diff no longer pastes the line numbers or +/- - Copying and pasting a diff no longer pastes the line numbers or +/-
- Add null check to formData when updating profile content to fix Firefox bug - Add null check to formData when updating profile content to fix Firefox bug
- Disable spellcheck and autocorrect for username field in admin page
- Delete tags using Rugged for performance reasons (Robert Schilling) - Delete tags using Rugged for performance reasons (Robert Schilling)
- Add Slack notifications when Wiki is edited (Sebastian Klier) - Add Slack notifications when Wiki is edited (Sebastian Klier)
- Diffs load at the correct point when linking from from number - Diffs load at the correct point when linking from from number
......
...@@ -105,6 +105,25 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart, ...@@ -105,6 +105,25 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart,
star, smile, etc.). Some good tips about giving feedback to merge requests is in star, smile, etc.). Some good tips about giving feedback to merge requests is in
the [Thoughtbot code review guide]. the [Thoughtbot code review guide].
## Feature Freeze
5 working days before the 22nd the stable branches for the upcoming release will
be frozen for major changes. Merge requests may still be merged into master
during this period. By freezing the stable branches prior to a release there's
no need to worry about last minute merge requests potentially breaking a lot of
things.
What is considered to be a major change is determined on a case by case basis as
this definition depends very much on the context of changes. For example, a 5
line change might have a big impact on the entire application. Ultimately the
decision will be made by those reviewing a merge request and the release
manager.
During the feature freeze all merge requests that are meant to go into the next
release should have the correct milestone assigned _and_ have the label
~"Pick into Stable" set. Merge requests without a milestone and this label will
not be merged into any stable branches.
## Copy & paste responses ## Copy & paste responses
### Improperly formatted issue ### Improperly formatted issue
......
...@@ -55,7 +55,6 @@ ...@@ -55,7 +55,6 @@
#= require_tree . #= require_tree .
#= require fuzzaldrin-plus #= require fuzzaldrin-plus
#= require cropper #= require cropper
#= require raven
window.slugify = (text) -> window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
......
...@@ -87,8 +87,8 @@ class @MergeRequestTabs ...@@ -87,8 +87,8 @@ class @MergeRequestTabs
if window.location.hash if window.location.hash
navBarHeight = $('.navbar-gitlab').outerHeight() navBarHeight = $('.navbar-gitlab').outerHeight()
$el = $("#{container} #{window.location.hash}") $el = $("#{container} #{window.location.hash}:not(.match)")
$.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length $.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length
# Activate a tab based on the current action # Activate a tab based on the current action
activateTab: (action) -> activateTab: (action) ->
...@@ -176,12 +176,12 @@ class @MergeRequestTabs ...@@ -176,12 +176,12 @@ class @MergeRequestTabs
if locationHash isnt '' if locationHash isnt ''
hashClassString = ".#{locationHash.replace('#', '')}" hashClassString = ".#{locationHash.replace('#', '')}"
$diffLine = $(locationHash) $diffLine = $("#{locationHash}:not(.match)", $('#diffs'))
if $diffLine.is ':not(tr)' if not $diffLine.is 'tr'
$diffLine = $("td#{locationHash}, td#{hashClassString}") $diffLine = $('#diffs').find("td#{locationHash}, td#{hashClassString}")
else else
$diffLine = $('td', $diffLine) $diffLine = $diffLine.find('td')
if $diffLine.length if $diffLine.length
$diffLine.addClass 'hll' $diffLine.addClass 'hll'
......
@raven =
init: ->
if gon.sentry_dsn?
Raven.config(gon.sentry_dsn, {
includePaths: [/gon.relative_url_root/]
ignoreErrors: [
# Random plugins/extensions
'top.GLOBALS',
# See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error. html
'originalCreateNotification',
'canvas.contentDocument',
'MyApp_RemoveAllHighlights',
'http://tt.epicplay.com',
'Can\'t find variable: ZiteReader',
'jigsaw is not defined',
'ComboSearch is not defined',
'http://loading.retry.widdit.com/',
'atomicFindClose',
# ISP "optimizing" proxy - `Cache-Control: no-transform` seems to
# reduce this. (thanks @acdha)
# See http://stackoverflow.com/questions/4113268
'bmi_SafeAddOnload',
'EBCallBackMessageReceived',
# See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx
'conduitPage'
],
ignoreUrls: [
# Chrome extensions
/extensions\//i,
/^chrome:\/\//i,
# Other plugins
/127\.0\.0\.1:4001\/isrunning/i, # Cacaoweb
/webappstoolbarba\.texthelp\.com\//i,
/metrics\.itunes\.apple\.com\.edgesuite\.net\//i
]
}).install()
if gon.current_user_id
Raven.setUserContext({
id: gon.current_user_id
})
$ ->
raven.init()
class @Todos class @Todos
constructor: (@name) -> constructor: (opts = {}) ->
{
@el = $('.js-todos-options')
} = opts
@perPage = @el.data('perPage')
@clearListeners() @clearListeners()
@initBtnListeners() @initBtnListeners()
...@@ -26,6 +32,7 @@ class @Todos ...@@ -26,6 +32,7 @@ class @Todos
dataType: 'json' dataType: 'json'
data: '_method': 'delete' data: '_method': 'delete'
success: (data) => success: (data) =>
@redirectIfNeeded data.count
@clearDone $this.closest('li') @clearDone $this.closest('li')
@updateBadges data @updateBadges data
...@@ -57,6 +64,40 @@ class @Todos ...@@ -57,6 +64,40 @@ class @Todos
$('.todos-pending .badge, .todos-pending-count').text data.count $('.todos-pending .badge, .todos-pending-count').text data.count
$('.todos-done .badge').text data.done_count $('.todos-done .badge').text data.done_count
getTotalPages: ->
@el.data('totalPages')
getCurrentPage: ->
@el.data('currentPage')
getTodosPerPage: ->
@el.data('perPage')
redirectIfNeeded: (total) ->
currPages = @getTotalPages()
currPage = @getCurrentPage()
# Refresh if no remaining Todos
if not total
location.reload()
return
# Do nothing if no pagination
return if not currPages
newPages = Math.ceil(total / @getTodosPerPage())
url = location.href # Includes query strings
# If new total of pages is different than we have now
if newPages isnt currPages
# Redirect to previous page if there's one available
if currPages > 1 and currPage is currPages
pageParams =
page: currPages - 1
url = gl.utils.mergeUrlParams(pageParams, url)
Turbolinks.visit(url)
goToTodoUrl: (e)-> goToTodoUrl: (e)->
todoLink = $(this).data('url') todoLink = $(this).data('url')
return unless todoLink return unless todoLink
......
...@@ -38,12 +38,14 @@ ...@@ -38,12 +38,14 @@
.filename { .filename {
&.old { &.old {
display: inline-block;
span.idiff { span.idiff {
background-color: #f8cbcb; background-color: #f8cbcb;
} }
} }
&.new { &.new {
display: inline-block;
span.idiff { span.idiff {
background-color: #a6f3a6; background-color: #a6f3a6;
} }
......
...@@ -183,6 +183,9 @@ ul.notes { ...@@ -183,6 +183,9 @@ ul.notes {
} }
} }
.author_link {
color: $gl-gray;
}
} }
.note-headline-light, .note-headline-light,
......
...@@ -38,13 +38,13 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -38,13 +38,13 @@ class Projects::CommitController < Projects::ApplicationController
end end
def cancel_builds def cancel_builds
ci_commit.builds.running_or_pending.each(&:cancel) ci_builds.running_or_pending.each(&:cancel)
redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha) redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha)
end end
def retry_builds def retry_builds
ci_commit.builds.latest.failed.each do |build| ci_builds.latest.failed.each do |build|
if build.retryable? if build.retryable?
Ci::Build.retry(build) Ci::Build.retry(build)
end end
...@@ -99,8 +99,12 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -99,8 +99,12 @@ class Projects::CommitController < Projects::ApplicationController
@commit ||= @project.commit(params[:id]) @commit ||= @project.commit(params[:id])
end end
def ci_commit def ci_commits
@ci_commit ||= project.ci_commit(commit.sha) @ci_commits ||= project.ci_commits.where(sha: commit.sha)
end
def ci_builds
@ci_builds ||= Ci::Build.where(commit: ci_commits)
end end
def define_show_vars def define_show_vars
...@@ -113,7 +117,8 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -113,7 +117,8 @@ class Projects::CommitController < Projects::ApplicationController
@diff_refs = [commit.parent || commit, commit] @diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count @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 end
def assign_change_commit_vars(mr_source_branch) def assign_change_commit_vars(mr_source_branch)
......
...@@ -321,6 +321,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -321,6 +321,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_widget_vars def define_widget_vars
@ci_commit = @merge_request.ci_commit @ci_commit = @merge_request.ci_commit
@ci_commits = [@ci_commit].compact
closes_issues closes_issues
end end
......
class Projects::ProjectMembersController < Projects::ApplicationController class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize # Authorize
before_action :authorize_admin_project_member!, except: :leave before_action :authorize_admin_project_member!, except: [:leave, :index]
def index def index
@project_members = @project.project_members @project_members = @project.project_members
......
...@@ -272,7 +272,6 @@ class IssuableFinder ...@@ -272,7 +272,6 @@ class IssuableFinder
items = items.without_label items = items.without_label
else else
items = items.with_label(label_names) items = items.with_label(label_names)
if projects if projects
items = items.where(labels: { project_id: projects }) items = items.where(labels: { project_id: projects })
end end
...@@ -321,7 +320,7 @@ class IssuableFinder ...@@ -321,7 +320,7 @@ class IssuableFinder
end end
def label_names def label_names
params[:label_name].split(',') params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
end end
def current_user_related? def current_user_related?
......
...@@ -4,14 +4,6 @@ module CiStatusHelper ...@@ -4,14 +4,6 @@ module CiStatusHelper
builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
end 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) def ci_status_with_icon(status, target = nil)
content = ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status) content = ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
klass = "ci-status ci-#{status}" klass = "ci-status ci-#{status}"
...@@ -47,10 +39,13 @@ module CiStatusHelper ...@@ -47,10 +39,13 @@ module CiStatusHelper
end end
def render_ci_status(ci_commit, tooltip_placement: 'auto left') def render_ci_status(ci_commit, tooltip_placement: 'auto left')
link_to ci_status_icon(ci_commit), # TODO: split this method into
# - render_commit_status
# - render_pipeline_status
link_to ci_icon_for_status(ci_commit.status),
ci_status_path(ci_commit), ci_status_path(ci_commit),
class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", 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 } data: { toggle: 'tooltip', placement: tooltip_placement }
end end
......
...@@ -25,6 +25,10 @@ module GitlabRoutingHelper ...@@ -25,6 +25,10 @@ module GitlabRoutingHelper
namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
end end
def project_pipelines_path(project, *args)
namespace_project_pipelines_path(project.namespace, project, *args)
end
def project_builds_path(project, *args) def project_builds_path(project, *args)
namespace_project_builds_path(project.namespace, project, *args) namespace_project_builds_path(project.namespace, project, *args)
end end
......
...@@ -84,6 +84,14 @@ module PageLayoutHelper ...@@ -84,6 +84,14 @@ module PageLayoutHelper
end end
end end
def nav(name = nil)
if name
@nav = name
else
@nav
end
end
def fluid_layout(enabled = false) def fluid_layout(enabled = false)
if @fluid_layout.nil? if @fluid_layout.nil?
@fluid_layout = (current_user && current_user.layout == "fluid") || enabled @fluid_layout = (current_user && current_user.layout == "fluid") || enabled
......
...@@ -144,6 +144,10 @@ module ProjectsHelper ...@@ -144,6 +144,10 @@ module ProjectsHelper
nav_tabs << :settings nav_tabs << :settings
end end
if can?(current_user, :read_project_member, project)
nav_tabs << :team
end
if can?(current_user, :read_issue, project) if can?(current_user, :read_issue, project)
nav_tabs << :issues nav_tabs << :issues
end end
......
...@@ -110,4 +110,12 @@ module TabHelper ...@@ -110,4 +110,12 @@ module TabHelper
'active' 'active'
end end
end end
def profile_tab_class
if controller.controller_path =~ /\Aprofiles/
return 'active'
end
'active' if current_controller?('oauth/applications')
end
end end
...@@ -37,8 +37,6 @@ ...@@ -37,8 +37,6 @@
module Ci module Ci
class Build < CommitStatus class Build < CommitStatus
LAZY_ATTRIBUTES = ['trace']
belongs_to :runner, class_name: 'Ci::Runner' belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
belongs_to :erased_by, class_name: 'User' belongs_to :erased_by, class_name: 'User'
...@@ -50,25 +48,17 @@ module Ci ...@@ -50,25 +48,17 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) } scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) } 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_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader
acts_as_taggable acts_as_taggable
# To prevent db load megabytes of data from trace
default_scope -> { select(Ci::Build.columns_without_lazy) }
before_destroy { project } before_destroy { project }
class << self after_create :execute_hooks
def columns_without_lazy
(column_names - LAZY_ATTRIBUTES).map do |column_name|
"#{table_name}.#{column_name}"
end
end
class << self
def last_month def last_month
where('created_at > ?', Date.today - 1.month) where('created_at > ?', Date.today - 1.month)
end end
...@@ -126,12 +116,16 @@ module Ci ...@@ -126,12 +116,16 @@ module Ci
end end
def retried? def retried?
!self.commit.latest_statuses_for_ref(self.ref).include?(self) !self.commit.statuses.latest.include?(self)
end
def retry
Ci::Build.retry(self)
end end
def depends_on_builds def depends_on_builds
# Get builds of the same type # 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 # Return builds from previous stages
latest_builds.where('stage_idx < ?', stage_idx) latest_builds.where('stage_idx < ?', stage_idx)
......
...@@ -19,21 +19,28 @@ ...@@ -19,21 +19,28 @@
module Ci module Ci
class Commit < ActiveRecord::Base class Commit < ActiveRecord::Base
extend Ci::Model extend Ci::Model
include Statuseable
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
has_many :statuses, class_name: 'CommitStatus' has_many :statuses, class_name: 'CommitStatus'
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
delegate :stages, to: :statuses
validates_presence_of :sha validates_presence_of :sha
validates_presence_of :status
validate :valid_commit_sha validate :valid_commit_sha
# Invalidate object and save if when touched
after_touch :update_state
def self.truncate_sha(sha) def self.truncate_sha(sha)
sha[0...8] sha[0...8]
end end
def to_param def self.stages
sha CommitStatus.where(commit: all).stages
end end
def project_id def project_id
...@@ -68,15 +75,20 @@ module Ci ...@@ -68,15 +75,20 @@ module Ci
nil nil
end end
def stage def branch?
running_or_pending = statuses.latest.running_or_pending.ordered !tag?
running_or_pending.first.try(:stage) end
def retryable?
builds.latest.any? do |build|
build.failed? && build.retryable?
end
end end
def create_builds(ref, tag, user, trigger_request = nil) def create_builds(user, trigger_request = nil)
return unless config_processor return unless config_processor
config_processor.stages.any? do |stage| 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
end end
...@@ -84,7 +96,7 @@ module Ci ...@@ -84,7 +96,7 @@ module Ci
return unless config_processor return unless config_processor
# don't create other builds if this one is retried # 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) return unless latest_builds.exists?(build.id)
# get list of stages after this build # get list of stages after this build
...@@ -92,88 +104,21 @@ module Ci ...@@ -92,88 +104,21 @@ module Ci
next_stages.delete(build.stage) next_stages.delete(build.stage)
# get status for all prior builds # get status for all prior builds
prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) } prior_builds = latest_builds.where.not(stage: next_stages)
status = Ci::Status.get_status(prior_builds) prior_status = prior_builds.status
# create builds for next stages based # create builds for next stages based
next_stages.any? do |stage| 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, prior_status, build.trigger_request).present?
end end
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
end
def retried def retried
@retried ||= (statuses.order(id: :desc) - statuses.latest) @retried ||= (statuses.order(id: :desc) - statuses.latest)
end 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?
end
def duration
duration_array = statuses.map(&:duration).compact
duration_array.reduce(:+).to_i
end
def started_at
@started_at ||= statuses.order('started_at ASC').first.try(:started_at)
end
def finished_at
@finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
end
def coverage def coverage
coverage_array = latest_statuses.map(&:coverage).compact coverage_array = statuses.latest.map(&:coverage).compact
if coverage_array.size >= 1 if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size) '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end end
...@@ -181,23 +126,29 @@ module Ci ...@@ -181,23 +126,29 @@ module Ci
def config_processor def config_processor
return nil unless ci_yaml_file return nil unless ci_yaml_file
@config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace) return @config_processor if defined?(@config_processor)
rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
save_yaml_error(e.message) @config_processor ||= begin
nil Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
rescue rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
save_yaml_error("Undefined error") save_yaml_error(e.message)
nil nil
rescue
save_yaml_error("Undefined error")
nil
end
end end
def ci_yaml_file def ci_yaml_file
return @ci_yaml_file if defined?(@ci_yaml_file)
@ci_yaml_file ||= begin @ci_yaml_file ||= begin
blob = project.repository.blob_at(sha, '.gitlab-ci.yml') blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
blob.load_all_data!(project.repository) blob.load_all_data!(project.repository)
blob.data blob.data
rescue
nil
end end
rescue
nil
end end
def skip_ci? def skip_ci?
...@@ -206,10 +157,23 @@ module Ci ...@@ -206,10 +157,23 @@ module Ci
private private
def update_state
statuses.reload
self.status = if yaml_errors.blank?
statuses.latest.status || 'skipped'
else
'failed'
end
self.started_at = statuses.started_at
self.finished_at = statuses.finished_at
self.duration = statuses.latest.duration
save
end
def save_yaml_error(error) def save_yaml_error(error)
return if self.yaml_errors? return if self.yaml_errors?
self.yaml_errors = error self.yaml_errors = error
save update_state
end end
end end
end end
...@@ -207,12 +207,13 @@ class Commit ...@@ -207,12 +207,13 @@ class Commit
@raw.short_id(7) @raw.short_id(7)
end end
def ci_commit def ci_commits
project.ci_commit(sha) @ci_commits ||= project.ci_commits.where(sha: sha)
end end
def status def status
ci_commit.try(:status) || :not_found return @status if defined?(@status)
@status ||= ci_commits.status
end end
def revert_branch_name def revert_branch_name
......
...@@ -33,30 +33,23 @@ ...@@ -33,30 +33,23 @@
# #
class CommitStatus < ActiveRecord::Base class CommitStatus < ActiveRecord::Base
include Statuseable
self.table_name = 'ci_builds' self.table_name = 'ci_builds'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
belongs_to :commit, class_name: 'Ci::Commit' belongs_to :commit, class_name: 'Ci::Commit', touch: true
belongs_to :user belongs_to :user
validates :commit, presence: true validates :commit, presence: true
validates :status, inclusion: { in: %w(pending running failed success canceled) }
validates_presence_of :name validates_presence_of :name
alias_attribute :author, :user alias_attribute :author, :user
scope :running, -> { where(status: 'running') } scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) }
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 :ordered, -> { order(:ref, :stage_idx, :name) } scope :ordered, -> { order(:ref, :stage_idx, :name) }
scope :for_ref, ->(ref) { where(ref: ref) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled']
state_machine :status, initial: :pending do state_machine :status, initial: :pending do
event :run do event :run do
...@@ -86,31 +79,24 @@ class CommitStatus < ActiveRecord::Base ...@@ -86,31 +79,24 @@ class CommitStatus < ActiveRecord::Base
after_transition [:pending, :running] => :success do |commit_status| after_transition [:pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status) MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
end end
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
state :success, value: 'success'
state :canceled, value: 'canceled'
end end
delegate :sha, :short_sha, to: :commit, prefix: false delegate :sha, :short_sha, to: :commit
# TODO: this should be removed with all references
def before_sha def before_sha
Gitlab::Git::BLANK_SHA commit.before_sha || Gitlab::Git::BLANK_SHA
end end
def started? def self.stages
!pending? && !canceled? && started_at order_by = 'max(stage_idx)'
group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact
end end
def active? def self.stages_status
running? || pending? all.stages.inject({}) do |h, stage|
end h[stage] = all.where(stage: stage).status
h
def complete? end
canceled? || success? || failed?
end end
def ignored? def ignored?
...@@ -118,11 +104,13 @@ class CommitStatus < ActiveRecord::Base ...@@ -118,11 +104,13 @@ class CommitStatus < ActiveRecord::Base
end end
def duration def duration
if started_at && finished_at duration =
finished_at - started_at if started_at && finished_at
elsif started_at finished_at - started_at
Time.now - started_at elsif started_at
end Time.now - started_at
end
duration
end end
def stuck? def stuck?
......
...@@ -37,7 +37,6 @@ module Issuable ...@@ -37,7 +37,6 @@ module Issuable
scope :closed, -> { with_state(:closed) } scope :closed, -> { with_state(:closed) }
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') } scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') } scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) } scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) } scope :join_project, -> { joins(:project) }
...@@ -122,6 +121,14 @@ module Issuable ...@@ -122,6 +121,14 @@ module Issuable
joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC") joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
end end
def with_label(title)
if title.is_a?(Array) && title.count > 1
joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}")
else
joins(:labels).where(labels: { title: title })
end
end
end end
def today? def today?
......
module Statuseable
extend ActiveSupport::Concern
AVAILABLE_STATUSES = %w(pending running success failed canceled skipped)
class_methods do
def status_sql
builds = all.select('count(*)').to_sql
success = all.success.select('count(*)').to_sql
ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored)
ignored ||= '0'
pending = all.pending.select('count(*)').to_sql
running = all.running.select('count(*)').to_sql
canceled = all.canceled.select('count(*)').to_sql
skipped = all.skipped.select('count(*)').to_sql
deduce_status = "(CASE
WHEN (#{builds})=0 THEN NULL
WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success'
WHEN (#{builds})=(#{pending}) THEN 'pending'
WHEN (#{builds})=(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{running})+(#{pending})>0 THEN 'running'
ELSE 'failed'
END)"
deduce_status
end
def status
all.pluck(self.status_sql).first
end
def duration
duration_array = all.map(&:duration).compact
duration_array.reduce(:+)
end
def started_at
all.minimum(:started_at)
end
def finished_at
all.maximum(:finished_at)
end
end
included do
validates :status, inclusion: { in: AVAILABLE_STATUSES }
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'
state :skipped, value: 'skipped'
end
scope :running, -> { where(status: 'running') }
scope :pending, -> { where(status: 'pending') }
scope :success, -> { where(status: 'success') }
scope :failed, -> { where(status: 'failed') }
scope :canceled, -> { where(status: 'canceled') }
scope :skipped, -> { where(status: 'skipped') }
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
...@@ -586,7 +586,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -586,7 +586,7 @@ class MergeRequest < ActiveRecord::Base
end end
def ci_commit 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 end
def diff_refs def diff_refs
......
...@@ -963,12 +963,12 @@ class Project < ActiveRecord::Base ...@@ -963,12 +963,12 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock !namespace.share_with_group_lock
end end
def ci_commit(sha) def ci_commit(sha, ref)
ci_commits.find_by(sha: sha) ci_commits.order(id: :desc).find_by(sha: sha, ref: ref)
end end
def ensure_ci_commit(sha) def ensure_ci_commit(sha, ref)
ci_commit(sha) || ci_commits.create(sha: sha) ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref)
end end
def enable_ci def enable_ci
......
module Ci module Ci
class CreateBuildsService class CreateBuildsService
def execute(commit, stage, ref, tag, user, trigger_request, status) def initialize(commit)
builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request) @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 # check when to create next build
builds_attrs = builds_attrs.select do |build_attrs| builds_attrs = builds_attrs.select do |build_attrs|
...@@ -17,7 +21,8 @@ module Ci ...@@ -17,7 +21,8 @@ module Ci
builds_attrs.map do |build_attrs| builds_attrs.map do |build_attrs|
# don't create the same build twice # 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, build_attrs.slice!(:name,
:commands, :commands,
:tag_list, :tag_list,
...@@ -26,17 +31,21 @@ module Ci ...@@ -26,17 +31,21 @@ module Ci
:stage, :stage,
:stage_idx) :stage_idx)
build_attrs.merge!(ref: ref, build_attrs.merge!(ref: @commit.ref,
tag: tag, tag: @commit.tag,
trigger_request: trigger_request, trigger_request: trigger_request,
user: user, user: user,
project: commit.project) project: @commit.project)
build = commit.builds.create!(build_attrs) @commit.builds.create!(build_attrs)
build.execute_hooks
build
end end
end end
end end
private
def config_processor
@config_processor ||= @commit.config_processor
end
end end
end end
...@@ -7,14 +7,14 @@ module Ci ...@@ -7,14 +7,14 @@ module Ci
# check if ref is tag # check if ref is tag
tag = project.repository.find_tag(ref).present? tag = project.repository.find_tag(ref).present?
ci_commit = project.ensure_ci_commit(commit.sha) ci_commit = project.ci_commits.create(sha: commit.sha, ref: ref, tag: tag)
trigger_request = trigger.trigger_requests.create!( trigger_request = trigger.trigger_requests.create!(
variables: variables, variables: variables,
commit: ci_commit, commit: ci_commit,
) )
if ci_commit.create_builds(ref, tag, nil, trigger_request) if ci_commit.create_builds(nil, trigger_request)
trigger_request trigger_request
end end
end end
......
...@@ -3,8 +3,9 @@ module Ci ...@@ -3,8 +3,9 @@ module Ci
def execute(project, opts) def execute(project, opts)
sha = opts[:sha] || ref_sha(project, opts[:ref]) sha = opts[:sha] || ref_sha(project, opts[:ref])
commit = project.ci_commits.find_by(sha: sha) ci_commits = project.ci_commits.where(sha: sha)
image_name = image_for_commit(commit) ci_commits = ci_commits.where(ref: opts[:ref]) if opts[:ref]
image_name = image_for_status(ci_commits.status)
image_path = Rails.root.join('public/ci', image_name) image_path = Rails.root.join('public/ci', image_name)
OpenStruct.new(path: image_path, name: image_name) OpenStruct.new(path: image_path, name: image_name)
...@@ -16,9 +17,9 @@ module Ci ...@@ -16,9 +17,9 @@ module Ci
project.commit(ref).try(:sha) if ref project.commit(ref).try(:sha) if ref
end end
def image_for_commit(commit) def image_for_status(status)
return 'build-unknown.svg' unless commit status ||= 'unknown'
'build-' + commit.status + ".svg" 'build-' + status + ".svg"
end end
end end
end end
...@@ -2,6 +2,7 @@ class CreateCommitBuildsService ...@@ -2,6 +2,7 @@ class CreateCommitBuildsService
def execute(project, user, params) def execute(project, user, params)
return false unless project.builds_enabled? return false unless project.builds_enabled?
before_sha = params[:checkout_sha] || params[:before]
sha = params[:checkout_sha] || params[:after] sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref] origin_ref = params[:ref]
...@@ -10,15 +11,16 @@ class CreateCommitBuildsService ...@@ -10,15 +11,16 @@ class CreateCommitBuildsService
end end
ref = Gitlab::Git.ref_name(origin_ref) ref = Gitlab::Git.ref_name(origin_ref)
tag = Gitlab::Git.tag_ref?(origin_ref)
# Skip branch removal # Skip branch removal
if sha == Gitlab::Git::BLANK_SHA if sha == Gitlab::Git::BLANK_SHA
return false return false
end end
commit = project.ci_commit(sha) commit = project.ci_commit(sha, ref)
unless commit 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 # Skip creating ci_commit when no gitlab-ci.yml is found
unless commit.ci_yaml_file unless commit.ci_yaml_file
...@@ -32,10 +34,10 @@ class CreateCommitBuildsService ...@@ -32,10 +34,10 @@ class CreateCommitBuildsService
# Skip creating builds for commits that have [ci skip] # Skip creating builds for commits that have [ci skip]
unless commit.skip_ci? unless commit.skip_ci?
# Create builds for commit # Create builds for commit
tag = Gitlab::Git.tag_ref?(origin_ref) commit.create_builds(user)
commit.create_builds(ref, tag, user)
end end
commit.touch
commit commit
end end
end end
...@@ -7,17 +7,17 @@ ...@@ -7,17 +7,17 @@
.form-group .form-group
= f.label :name, class: 'control-label' = f.label :name, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :name, required: true, autocomplete: "off", class: 'form-control' = f.text_field :name, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required %span.help-inline * required
.form-group .form-group
= f.label :username, class: 'control-label' = f.label :username, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :username, required: true, autocomplete: "off", class: 'form-control' = f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control'
%span.help-inline * required %span.help-inline * required
.form-group .form-group
= f.label :email, class: 'control-label' = f.label :email, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :email, required: true, autocomplete: "off", class: 'form-control' = f.text_field :email, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required %span.help-inline * required
- if @user.new_record? - if @user.new_record?
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
.prepend-top-default .prepend-top-default
- if @todos.any? - if @todos.any?
.js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
- @todos.group_by(&:project).each do |group| - @todos.group_by(&:project).each do |group|
.panel.panel-default.panel-small.js-todos-list .panel.panel-default.panel-small.js-todos-list
- project = group[0] - project = group[0]
......
- page_title "Applications" - page_title "Applications"
- header_title page_title, applications_profile_path
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
......
...@@ -25,6 +25,10 @@ ...@@ -25,6 +25,10 @@
.content-wrapper .content-wrapper
= render "layouts/flash" = render "layouts/flash"
= yield :flash_message = yield :flash_message
- if defined?(nav) && nav
.layout-nav
%div{ class: container_class }
= render "layouts/nav/#{nav}"
%div{ class: (container_class unless @no_container) } %div{ class: (container_class unless @no_container) }
.content .content
.clearfix .clearfix
......
...@@ -6,6 +6,6 @@ ...@@ -6,6 +6,6 @@
= yield :scripts_body_top = yield :scripts_body_top
= render "layouts/header/default", title: header_title = render "layouts/header/default", title: header_title
= render 'layouts/page', sidebar: sidebar = render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body = yield :scripts_body
...@@ -48,8 +48,7 @@ ...@@ -48,8 +48,7 @@
%span %span
Help Help
%li.separate-item = nav_link(html_options: {class: profile_tab_class}) do
= nav_link(controller: :profile) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw') = icon('user fw')
%span %span
......
%ul.nav.nav-sidebar %ul.nav-links
= nav_link do
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
%li.separate-item
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do = link_to profile_path, title: 'Profile Settings' do
= icon('user fw') = icon('user fw')
%span %span
Profile Settings Profile
= nav_link(controller: [:accounts, :two_factor_auths]) do = nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account' do = link_to profile_account_path, title: 'Account' do
= icon('gear fw') = icon('gear fw')
...@@ -27,7 +19,6 @@ ...@@ -27,7 +19,6 @@
= icon('envelope-o fw') = icon('envelope-o fw')
%span %span
Emails Emails
%span.count= number_with_delimiter(current_user.emails.count + 1)
- unless current_user.ldap_user? - unless current_user.ldap_user?
= nav_link(controller: :passwords) do = nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do = link_to edit_profile_password_path, title: 'Password' do
...@@ -45,7 +36,6 @@ ...@@ -45,7 +36,6 @@
= icon('key fw') = icon('key fw')
%span %span
SSH Keys SSH Keys
%span.count= number_with_delimiter(current_user.keys.count)
= nav_link(controller: :preferences) do = nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do = link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon? -# TODO (rspeicher): Better icon?
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
Merge Requests Merge Requests
%span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) %span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
- if project_nav_tab? :settings - if project_nav_tab? :team
= nav_link(controller: [:project_members, :teams]) do = nav_link(controller: [:project_members, :teams]) do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
= icon('users fw') = icon('users fw')
......
- page_title "Profile Settings" - page_title "Profile Settings"
- header_title "Profile Settings", profile_path unless header_title - header_title "Profile Settings", profile_path unless header_title
- sidebar "profile" - sidebar "dashboard"
- nav "profile"
= render template: "layouts/application" = render template: "layouts/application"
- page_title "Account" - page_title "Account"
- header_title page_title, profile_account_path
- if current_user.ldap_user? - if current_user.ldap_user?
.alert.alert-info .alert.alert-info
......
- page_title "Audit Log" - page_title "Audit Log"
- header_title page_title, audit_log_profile_path
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
......
- page_title "Emails" - page_title "Emails"
- header_title page_title, profile_emails_path
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
......
- page_title "SSH Keys" - page_title "SSH Keys"
- header_title page_title, profile_keys_path
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
......
- page_title "Notifications" - page_title "Notifications"
- header_title page_title, profile_notifications_path
%div %div
- if @user.errors.any? - if @user.errors.any?
......
- page_title "Password" - page_title "Password"
- header_title page_title, edit_profile_password_path
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
......
- page_title 'Preferences' - page_title 'Preferences'
- header_title page_title, profile_preferences_path
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f| = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f|
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
......
...@@ -55,6 +55,9 @@ ...@@ -55,6 +55,9 @@
%li %li
gcovr (C/C++) - gcovr (C/C++) -
%code ^TOTAL.*\s+(\d+\%)$ %code ^TOTAL.*\s+(\d+\%)$
%li
tap --coverage-report=text-summary (Node.js) -
%code ^Statements\s*:\s*([^%]+)
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
......
.project-last-commit .project-last-commit
- ci_commit = project.ci_commit(commit.sha) - if commit.status
- if ci_commit = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do
= link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do = ci_icon_for_status(commit.status)
= ci_status_icon(ci_commit) = ci_label_for_status(commit.status)
= ci_status_label(ci_commit)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" = 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" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
......
- if @lines.present? - if @lines.present?
- if @form.unfold? && @form.since != 1 && !@form.bottom? - if @form.unfold? && @form.since != 1 && !@form.bottom?
%tr.line_holder{ id: @form.since } %tr.line_holder
= render "projects/diffs/match_line", { line: @match_line, = render "projects/diffs/match_line", { line: @match_line,
line_old: @form.since, line_new: @form.since, bottom: false, new_file: false } line_old: @form.since, line_new: @form.since, bottom: false, new_file: false }
- @lines.each_with_index do |line, index| - @lines.each_with_index do |line, index|
- line_new = index + @form.since - line_new = index + @form.since
- line_old = line_new - @form.offset - line_old = line_new - @form.offset
%tr.line_holder %tr.line_holder{ id: line_old }
%td.old_line.diff-line-num{ data: { linenumber: line_old } } %td.old_line.diff-line-num{ data: { linenumber: line_old } }
= link_to raw(line_old), "#" = link_to raw(line_old), "##{line_old}"
%td.new_line.diff-line-num{ data: { linenumber: line_old } } %td.new_line.diff-line-num{ data: { linenumber: line_old } }
= link_to raw(line_new) , "#" = link_to raw(line_new) , "##{line_old}"
%td.line_content.noteable_line==#{' ' * @form.indent}#{line} %td.line_content.noteable_line==#{' ' * @form.indent}#{line}
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc - if @form.unfold? && @form.bottom? && @form.to < @blob.loc
......
...@@ -58,6 +58,6 @@ ...@@ -58,6 +58,6 @@
%th Coverage %th Coverage
%th %th
= render @builds, commit_sha: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled? = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
= paginate @builds, theme: 'gitlab' = paginate @builds, theme: 'gitlab'
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
= link_to "merge request #{merge_request.to_reference}", merge_request_path(merge_request) = link_to "merge request #{merge_request.to_reference}", merge_request_path(merge_request)
#up-build-trace #up-build-trace
- builds = @build.commit.matrix_builds(@build) - builds = @build.commit.builds.latest.to_a
- if builds.size > 1 - if builds.size > 1
%ul.nav-links.no-top.no-bottom %ul.nav-links.no-top.no-bottom
- builds.each do |build| - builds.each do |build|
......
...@@ -19,11 +19,12 @@ ...@@ -19,11 +19,12 @@
%td %td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
%td - if defined?(ref) && ref
- if build.ref %td
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) - if build.ref
- else = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
.light none - else
.light none
- if defined?(runner) && runner - if defined?(runner) && runner
%td %td
...@@ -48,6 +49,8 @@ ...@@ -48,6 +49,8 @@
%span.label.label-info triggered %span.label.label-info triggered
- if build.try(:allow_failure) - if build.try(:allow_failure)
%span.label.label-danger allowed to fail %span.label.label-danger allowed to fail
- if defined?(retried) && retried
%span.label.label-warning retried
%td.duration %td.duration
- if build.duration - if build.duration
......
.gray-content-block.middle-block - @ci_commits.each do |ci_commit|
.pull-right = render "ci_commit", ci_commit: ci_commit
- 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
.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, 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(@project.namespace, @project, ci_commit.sha), 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 ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @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 @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
...@@ -43,12 +43,12 @@ ...@@ -43,12 +43,12 @@
- @commit.parents.each do |parent| - @commit.parents.each do |parent|
= link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace"
- if @ci_commit - if @commit.status
.pull-right .pull-right
= link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do
= ci_status_icon(@ci_commit) = ci_icon_for_status(@commit.status)
build: build:
= ci_status_label(@ci_commit) = ci_label_for_status(@commit.status)
.commit-info-row.branches .commit-info-row.branches
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
.prepend-top-default .prepend-top-default
= render "commit_box" = render "commit_box"
- if @ci_commit - if @commit.status
= render "ci_menu" = render "ci_menu"
- else - else
%div.block-connector %div.block-connector
......
...@@ -4,9 +4,8 @@ ...@@ -4,9 +4,8 @@
- notes = commit.notes - notes = commit.notes
- note_count = notes.user.count - 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 = [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 = cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
...@@ -17,8 +16,8 @@ ...@@ -17,8 +16,8 @@
%a.text-expander.js-toggle-button ... %a.text-expander.js-toggle-button ...
.pull-right .pull-right
- if ci_commit - if commit.status
= render_ci_status(ci_commit) = render_ci_status(commit)
= clipboard_button(clipboard_text: commit.id) = clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
......
...@@ -15,12 +15,13 @@ ...@@ -15,12 +15,13 @@
- if defined?(commit_sha) && commit_sha - if defined?(commit_sha) && commit_sha
%td %td
= link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace" = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace"
%td - if defined?(ref) && ref
- if generic_commit_status.ref %td
= link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) - if generic_commit_status.ref
- else = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
.light none - else
.light none
- if defined?(runner) && runner - if defined?(runner) && runner
%td %td
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
- @related_branches.each do |branch| - @related_branches.each do |branch|
%li %li
- sha = @project.repository.find_branch(branch).target - 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 - if ci_commit
%span.related-branch-ci-status %span.related-branch-ci-status
= render_ci_status(ci_commit) = render_ci_status(ci_commit)
......
- page_title "#{@merge_request.title} (#{merge_request.to_reference}", "Merge Requests" - page_title "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
= render "header_title" = render "header_title"
.merge-request .merge-request
......
= render "projects/commit/builds", link_to_commit: true = render "projects/commit/ci_commit", ci_commit: @ci_commit, link_to_commit: true
...@@ -6,9 +6,8 @@ ...@@ -6,9 +6,8 @@
- css_class = '' unless local_assigns[:css_class] - css_class = '' unless local_assigns[:css_class]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit - 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 - 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 = [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.try(:status)
%li.project-row{ class: css_class } %li.project-row{ class: css_class }
= cache(cache_key) do = cache(cache_key) do
...@@ -16,9 +15,9 @@ ...@@ -16,9 +15,9 @@
- if project.main_language - if project.main_language
%span %span
= project.main_language = project.main_language
- if ci_commit - if project.commit.try(:status)
%span %span
= render_ci_status(ci_commit) = render_ci_status(project.commit)
- if forks - if forks
%span %span
= icon('code-fork') = icon('code-fork')
......
...@@ -19,7 +19,7 @@ class Gitlab::Seeder::Builds ...@@ -19,7 +19,7 @@ class Gitlab::Seeder::Builds
commits = @project.repository.commits('master', nil, 5) commits = @project.repository.commits('master', nil, 5)
commits_sha = commits.map { |commit| commit.raw.id } commits_sha = commits.map { |commit| commit.raw.id }
commits_sha.map do |sha| commits_sha.map do |sha|
@project.ensure_ci_commit(sha) @project.ensure_ci_commit(sha, 'master')
end end
rescue rescue
[] []
......
...@@ -4,6 +4,8 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration ...@@ -4,6 +4,8 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration
def up def up
return unless Gitlab::Database.postgresql? return unless Gitlab::Database.postgresql?
create_trigrams_extension
unless trigrams_enabled? unless trigrams_enabled?
raise 'You must enable the pg_trgm extension. You can do so by running ' \ raise 'You must enable the pg_trgm extension. You can do so by running ' \
'"CREATE EXTENSION pg_trgm;" as a PostgreSQL super user, this must be ' \ '"CREATE EXTENSION pg_trgm;" as a PostgreSQL super user, this must be ' \
...@@ -37,6 +39,15 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration ...@@ -37,6 +39,15 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration
row && row['enabled'] == 't' ? true : false row && row['enabled'] == 't' ? true : false
end end
def create_trigrams_extension
# This may not work if the user doesn't have permission. We attempt in
# case we do have permission, particularly for test/dev environments.
begin
enable_extension 'pg_trgm'
rescue
end
end
def to_index def to_index
{ {
ci_runners: [:token, :description], ci_runners: [:token, :description],
......
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
add_column :ci_commits, :duration, :integer
end
end
class UpdateCiCommit < ActiveRecord::Migration
# This migration can be run online, but needs to be executed for the second time after restarting Unicorn workers
# Otherwise Offline migration should be used.
def change
execute("UPDATE ci_commits SET status=#{status}, ref=#{ref}, tag=#{tag} WHERE status IS NULL")
end
private
def status
builds = '(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id)'
success = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='success')"
ignored = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND (status='failed' OR status='canceled') AND allow_failure)"
pending = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='pending')"
running = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='running')"
canceled = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='canceled')"
"(CASE
WHEN #{builds}=0 THEN 'skipped'
WHEN #{builds}=#{success}+#{ignored} THEN 'success'
WHEN #{builds}=#{pending} THEN 'pending'
WHEN #{builds}=#{canceled} THEN 'canceled'
WHEN #{running}+#{pending}>0 THEN 'running'
ELSE 'failed'
END)"
end
def ref
'(SELECT ref FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)'
end
def tag
'(SELECT tag FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)'
end
end
class AddCiCommitIndexes < ActiveRecord::Migration
disable_ddl_transaction!
def change
add_index :ci_commits, [:gl_project_id, :sha], index_options
add_index :ci_commits, [:gl_project_id, :status], index_options
add_index :ci_commits, [:status], index_options
end
private
def index_options
if Gitlab::Database.postgresql?
{ algorithm: :concurrently }
else
{ }
end
end
end
...@@ -171,14 +171,21 @@ ActiveRecord::Schema.define(version: 20160419120017) do ...@@ -171,14 +171,21 @@ ActiveRecord::Schema.define(version: 20160419120017) do
t.text "yaml_errors" t.text "yaml_errors"
t.datetime "committed_at" t.datetime "committed_at"
t.integer "gl_project_id" t.integer "gl_project_id"
t.string "status"
t.datetime "started_at"
t.datetime "finished_at"
t.integer "duration"
end end
add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree
add_index "ci_commits", ["gl_project_id", "status"], name: "index_ci_commits_on_gl_project_id_and_status", using: :btree
add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree
add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree
add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree
add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree
add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree
add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree
add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree
create_table "ci_events", force: :cascade do |t| create_table "ci_events", force: :cascade do |t|
t.integer "project_id" t.integer "project_id"
......
...@@ -22,4 +22,8 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps ...@@ -22,4 +22,8 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps
step 'the active main tab should be Audit Log' do step 'the active main tab should be Audit Log' do
ensure_active_main_tab('Audit Log') ensure_active_main_tab('Audit Log')
end end
def ensure_active_main_tab(content)
expect(find('.layout-nav li.active')).to have_content(content)
end
end end
...@@ -519,7 +519,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -519,7 +519,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step '"Bug NS-05" has CI status' do step '"Bug NS-05" has CI status' do
project = merge_request.source_project project = merge_request.source_project
project.enable_ci project.enable_ci
ci_commit = create :ci_commit, project: project, sha: merge_request.last_commit.id ci_commit = create :ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch
create :ci_build, commit: ci_commit create :ci_build, commit: ci_commit
end end
......
...@@ -10,16 +10,16 @@ module SharedBuilds ...@@ -10,16 +10,16 @@ module SharedBuilds
end end
step 'project has a recent build' do step 'project has a recent build' do
@ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha) @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha, ref: 'master')
@build = create(:ci_build_with_coverage, commit: @ci_commit) @build = create(:ci_build_with_coverage, commit: @ci_commit)
end end
step 'recent build is successful' do step 'recent build is successful' do
@build.update_column(:status, 'success') @build.update(status: 'success')
end end
step 'recent build failed' do step 'recent build failed' do
@build.update_column(:status, 'failed') @build.update(status: 'failed')
end end
step 'project has another build that is running' do step 'project has another build that is running' do
......
...@@ -234,7 +234,7 @@ module SharedProject ...@@ -234,7 +234,7 @@ module SharedProject
step 'project "Shop" has CI build' do step 'project "Shop" has CI build' do
project = Project.find_by(name: "Shop") project = Project.find_by(name: "Shop")
create :ci_commit, project: project, sha: project.commit.sha create :ci_commit, project: project, sha: project.commit.sha, ref: 'master', status: 'skipped'
end end
step 'I should see last commit with CI status' do step 'I should see last commit with CI status' do
......
...@@ -21,10 +21,9 @@ module API ...@@ -21,10 +21,9 @@ module API
authorize!(:read_commit_status, user_project) authorize!(:read_commit_status, user_project)
not_found!('Commit') unless user_project.commit(params[:sha]) not_found!('Commit') unless user_project.commit(params[:sha])
ci_commit = user_project.ci_commit(params[:sha])
return [] unless ci_commit
statuses = ci_commit.statuses ci_commits = user_project.ci_commits.where(sha: params[:sha])
statuses = ::CommitStatus.where(commit: ci_commits)
statuses = statuses.latest unless parse_boolean(params[:all]) statuses = statuses.latest unless parse_boolean(params[:all])
statuses = statuses.where(ref: params[:ref]) if params[:ref].present? statuses = statuses.where(ref: params[:ref]) if params[:ref].present?
statuses = statuses.where(stage: params[:stage]) if params[:stage].present? statuses = statuses.where(stage: params[:stage]) if params[:stage].present?
...@@ -51,7 +50,21 @@ module API ...@@ -51,7 +50,21 @@ module API
commit = @project.commit(params[:sha]) commit = @project.commit(params[:sha])
not_found! 'Commit' unless commit not_found! 'Commit' unless commit
ci_commit = @project.ensure_ci_commit(commit.sha) # Since the CommitStatus is attached to Ci::Commit (in the future Pipeline)
# We need to always have the pipeline object
# To have a valid pipeline object that can be attached to specific MR
# Other CI service needs to send `ref`
# If we don't receive it, we will attach the CommitStatus to
# the first found branch on that commit
ref = params[:ref]
unless ref
branches = @project.repository.branch_names_contains(commit.sha)
not_found! 'References for commit' if branches.none?
ref = branches.first
end
ci_commit = @project.ensure_ci_commit(commit.sha, ref)
name = params[:name] || params[:context] name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref]) status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref])
......
...@@ -18,9 +18,7 @@ module Banzai ...@@ -18,9 +18,7 @@ module Banzai
def references_in(text, pattern = Label.reference_pattern) def references_in(text, pattern = Label.reference_pattern)
text.gsub(pattern) do |match| text.gsub(pattern) do |match|
project = project_from_ref($~[:project]) label = find_label($~[:project], $~[:label_id], $~[:label_name])
params = label_params($~[:label_id].to_i, $~[:label_name])
label = project.labels.find_by(params)
if label if label
yield match, label.id, $~[:project], $~ yield match, label.id, $~[:project], $~
...@@ -30,18 +28,12 @@ module Banzai ...@@ -30,18 +28,12 @@ module Banzai
end end
end end
def url_for_object(label, project) def find_label(project_ref, label_id, label_name)
h = Gitlab::Routing.url_helpers project = project_from_ref(project_ref)
h.namespace_project_issues_url(project.namespace, project, label_name: label.name, return unless project
only_path: context[:only_path])
end
def object_link_text(object, matches) label_params = label_params(label_id, label_name)
if context[:project] == object.project project.labels.find_by(label_params)
LabelsHelper.render_colored_label(object)
else
LabelsHelper.render_colored_cross_project_label(object)
end
end end
# Parameters to pass to `Label.find_by` based on the given arguments # Parameters to pass to `Label.find_by` based on the given arguments
...@@ -55,7 +47,21 @@ module Banzai ...@@ -55,7 +47,21 @@ module Banzai
if name if name
{ name: name.tr('"', '') } { name: name.tr('"', '') }
else else
{ id: id } { id: id.to_i }
end
end
def url_for_object(label, project)
h = Gitlab::Routing.url_helpers
h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
only_path: context[:only_path])
end
def object_link_text(object, matches)
if context[:project] == object.project
LabelsHelper.render_colored_label(object)
else
LabelsHelper.render_colored_cross_project_label(object)
end end
end end
end end
......
module Ci
class Status
def self.get_status(statuses)
if statuses.none?
'skipped'
elsif statuses.all? { |status| status.success? || status.ignored? }
'success'
elsif statuses.all?(&:pending?)
'pending'
elsif statuses.any?(&:running?) || statuses.any?(&:pending?)
'running'
elsif statuses.all?(&:canceled?)
'canceled'
else
'failed'
end
end
end
end
...@@ -8,7 +8,6 @@ module Gitlab ...@@ -8,7 +8,6 @@ module Gitlab
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = help_shortcuts_path gon.shortcuts_path = help_shortcuts_path
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
gon.sentry_dsn = ApplicationSetting.current.sentry_dsn if Rails.env.production?
if current_user if current_user
gon.current_user_id = current_user.id gon.current_user_id = current_user.id
......
...@@ -29,8 +29,8 @@ module Gitlab ...@@ -29,8 +29,8 @@ module Gitlab
"in #{GRACE_TIME} seconds" "in #{GRACE_TIME} seconds"
sleep(GRACE_TIME) sleep(GRACE_TIME)
Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}" Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid}"
Process.kill('SIGUSR1', Process.pid) Process.kill('SIGTERM', Process.pid)
Sidekiq.logger.warn "waiting #{SHUTDOWN_WAIT} seconds before sending "\ Sidekiq.logger.warn "waiting #{SHUTDOWN_WAIT} seconds before sending "\
"#{SHUTDOWN_SIGNAL} to PID #{Process.pid}" "#{SHUTDOWN_SIGNAL} to PID #{Process.pid}"
......
...@@ -46,4 +46,20 @@ describe Projects::ProjectMembersController do ...@@ -46,4 +46,20 @@ describe Projects::ProjectMembersController do
end end
end end
end end
describe '#index' do
let(:project) { create(:project, :private) }
context 'when user is member' do
let(:member) { create(:user) }
before do
project.team << [member, :guest]
sign_in(member)
get :index, namespace_id: project.namespace.to_param, project_id: project.to_param
end
it { expect(response.status).to eq(200) }
end
end
end end
require 'rails_helper' require 'rails_helper'
feature 'Issue filtering by Labels', feature: true do feature 'Issue filtering by Labels', feature: true do
include WaitForAjax
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let!(:user) { create(:user)} let!(:user) { create(:user)}
let!(:label) { create(:label, project: project) } let!(:label) { create(:label, project: project) }
before do before do
['bug', 'feature', 'enhancement'].each do |title| bug = create(:label, project: project, title: 'bug')
create(:label, feature = create(:label, project: project, title: 'feature')
project: project, enhancement = create(:label, project: project, title: 'enhancement')
title: title)
end
issue1 = create(:issue, title: "Bugfix1", project: project) issue1 = create(:issue, title: "Bugfix1", project: project)
issue1.labels << project.labels.find_by(title: 'bug') issue1.labels << bug
issue2 = create(:issue, title: "Bugfix2", project: project) issue2 = create(:issue, title: "Bugfix2", project: project)
issue2.labels << project.labels.find_by(title: 'bug') issue2.labels << bug
issue2.labels << project.labels.find_by(title: 'enhancement') issue2.labels << enhancement
issue3 = create(:issue, title: "Feature1", project: project) issue3 = create(:issue, title: "Feature1", project: project)
issue3.labels << project.labels.find_by(title: 'feature') issue3.labels << feature
project.team << [user, :master] project.team << [user, :master]
login_as(user) login_as(user)
...@@ -31,10 +31,10 @@ feature 'Issue filtering by Labels', feature: true do ...@@ -31,10 +31,10 @@ feature 'Issue filtering by Labels', feature: true do
context 'filter by label bug', js: true do context 'filter by label bug', js: true do
before do before do
page.find('.js-label-select').click page.find('.js-label-select').click
sleep 0.5 wait_for_ajax
execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
sleep 2 wait_for_ajax
end end
it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do
...@@ -59,10 +59,10 @@ feature 'Issue filtering by Labels', feature: true do ...@@ -59,10 +59,10 @@ feature 'Issue filtering by Labels', feature: true do
context 'filter by label feature', js: true do context 'filter by label feature', js: true do
before do before do
page.find('.js-label-select').click page.find('.js-label-select').click
sleep 0.5 wait_for_ajax
execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()")
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
sleep 2 wait_for_ajax
end end
it 'should show issue "Feature1" in issues list' do it 'should show issue "Feature1" in issues list' do
...@@ -87,10 +87,10 @@ feature 'Issue filtering by Labels', feature: true do ...@@ -87,10 +87,10 @@ feature 'Issue filtering by Labels', feature: true do
context 'filter by label enhancement', js: true do context 'filter by label enhancement', js: true do
before do before do
page.find('.js-label-select').click page.find('.js-label-select').click
sleep 0.5 wait_for_ajax
execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()")
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
sleep 2 wait_for_ajax
end end
it 'should show issue "Bugfix2" in issues list' do it 'should show issue "Bugfix2" in issues list' do
...@@ -115,20 +115,16 @@ feature 'Issue filtering by Labels', feature: true do ...@@ -115,20 +115,16 @@ feature 'Issue filtering by Labels', feature: true do
context 'filter by label enhancement or feature', js: true do context 'filter by label enhancement or feature', js: true do
before do before do
page.find('.js-label-select').click page.find('.js-label-select').click
sleep 0.5 wait_for_ajax
execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()")
execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()")
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
sleep 2 wait_for_ajax
end end
it 'should show issue "Bugfix2" or "Feature1" in issues list' do it 'should not show "Bugfix1" or "Feature1" in issues list' do
expect(page).to have_content "Bugfix2"
expect(page).to have_content "Feature1"
end
it 'should not show "Bugfix1" in issues list' do
expect(page).not_to have_content "Bugfix1" expect(page).not_to have_content "Bugfix1"
expect(page).not_to have_content "Feature1"
end end
it 'should show label "enhancement" and "feature" in filtered-labels' do it 'should show label "enhancement" and "feature" in filtered-labels' do
...@@ -141,19 +137,18 @@ feature 'Issue filtering by Labels', feature: true do ...@@ -141,19 +137,18 @@ feature 'Issue filtering by Labels', feature: true do
end end
end end
context 'filter by label enhancement or bug in issues list', js: true do context 'filter by label enhancement and bug in issues list', js: true do
before do before do
page.find('.js-label-select').click page.find('.js-label-select').click
sleep 0.5 wait_for_ajax
execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()")
execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
sleep 2 wait_for_ajax
end end
it 'should show issue "Bugfix2" or "Bugfix1" in issues list' do it 'should show issue "Bugfix2" in issues list' do
expect(page).to have_content "Bugfix2" expect(page).to have_content "Bugfix2"
expect(page).to have_content "Bugfix1"
end end
it 'should not show "Feature1"' do it 'should not show "Feature1"' do
......
...@@ -101,12 +101,12 @@ describe "Internal Project Access", feature: true do ...@@ -101,12 +101,12 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for master }
it { is_expected.to be_denied_for developer } it { is_expected.to be_allowed_for developer }
it { is_expected.to be_denied_for reporter } it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_denied_for guest } it { is_expected.to be_allowed_for guest }
it { is_expected.to be_denied_for :user } it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor } it { is_expected.to be_denied_for :visitor }
it { is_expected.to be_denied_for :external }
end end
describe "GET /:project_path/blob" do describe "GET /:project_path/blob" do
......
...@@ -101,9 +101,9 @@ describe "Private Project Access", feature: true do ...@@ -101,9 +101,9 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for master }
it { is_expected.to be_denied_for developer } it { is_expected.to be_allowed_for developer }
it { is_expected.to be_denied_for reporter } it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_denied_for guest } it { is_expected.to be_allowed_for guest }
it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor } it { is_expected.to be_denied_for :visitor }
......
...@@ -101,12 +101,12 @@ describe "Public Project Access", feature: true do ...@@ -101,12 +101,12 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for master }
it { is_expected.to be_denied_for developer } it { is_expected.to be_allowed_for developer }
it { is_expected.to be_denied_for reporter } it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_denied_for guest } it { is_expected.to be_allowed_for guest }
it { is_expected.to be_denied_for :user } it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external } it { is_expected.to be_allowed_for :visitor }
it { is_expected.to be_denied_for :visitor } it { is_expected.to be_allowed_for :external }
end end
describe "GET /:project_path/builds" do describe "GET /:project_path/builds" do
......
require 'spec_helper'
describe 'Dashboard Todos', feature: true do
let(:user){ create(:user) }
let(:author){ create(:user) }
let(:project){ create(:project) }
let(:issue){ create(:issue) }
let(:todos_per_page){ Todo.default_per_page }
let(:todos_total){ todos_per_page + 1 }
describe 'GET /dashboard/todos' do
context 'User does not have todos' do
before do
login_as(user)
visit dashboard_todos_path
end
it 'shows "All done" message' do
expect(page).to have_content "You're all done!"
end
end
context 'User has a todo', js: true do
before do
create(:todo, :mentioned, user: user, project: project, target: issue, author: author)
login_as(user)
visit dashboard_todos_path
end
it 'todo is present' do
expect(page).to have_selector('.todos-list .todo', count: 1)
end
describe 'deleting the todo' do
before do
first('.done-todo').click
end
it 'is removed from the list' do
expect(page).not_to have_selector('.todos-list .todo')
end
it 'shows "All done" message' do
expect(page).to have_content("You're all done!")
end
end
end
context 'User has multiple pages of Todos' do
let(:todo_total_pages){ (todos_total.to_f/todos_per_page).ceil }
before do
todos_total.times do
create(:todo, :mentioned, user: user, project: project, target: issue, author: author)
end
login_as(user)
visit dashboard_todos_path
end
it 'is paginated' do
expect(page).to have_selector('.gl-pagination')
end
it 'is has the right number of pages' do
expect(page).to have_selector('.gl-pagination .page', count: todo_total_pages)
end
describe 'deleting last todo from last page', js: true do
it 'redirects to the previous page' do
page.within('.gl-pagination') do
click_link todo_total_pages.to_s
end
first('.done-todo').click
expect(page).to have_content(Todo.last.body)
end
end
end
end
end
...@@ -6,8 +6,8 @@ describe CiStatusHelper do ...@@ -6,8 +6,8 @@ describe CiStatusHelper do
let(:success_commit) { double("Ci::Commit", status: 'success') } let(:success_commit) { double("Ci::Commit", status: 'success') }
let(:failed_commit) { double("Ci::Commit", status: 'failed') } let(:failed_commit) { double("Ci::Commit", status: 'failed') }
describe 'ci_status_icon' do describe 'ci_icon_for_status' do
it { expect(helper.ci_status_icon(success_commit)).to include('fa-check') } it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') }
it { expect(helper.ci_status_icon(failed_commit)).to include('fa-close') } it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') }
end end
end end
...@@ -178,27 +178,37 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do ...@@ -178,27 +178,37 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
end end
describe 'cross project label references' do describe 'cross project label references' do
let(:another_project) { create(:empty_project, :public) } context 'valid project referenced' do
let(:project_name) { another_project.name_with_namespace } let(:another_project) { create(:empty_project, :public) }
let(:label) { create(:label, project: another_project, color: '#00ff00') } let(:project_name) { another_project.name_with_namespace }
let(:reference) { label.to_reference(project) } let(:label) { create(:label, project: another_project, color: '#00ff00') }
let(:reference) { label.to_reference(project) }
let!(:result) { reference_filter("See #{reference}") } let!(:result) { reference_filter("See #{reference}") }
it 'points to referenced project issues page' do it 'points to referenced project issues page' do
expect(result.css('a').first.attr('href')) expect(result.css('a').first.attr('href'))
.to eq urls.namespace_project_issues_url(another_project.namespace, .to eq urls.namespace_project_issues_url(another_project.namespace,
another_project, another_project,
label_name: label.name) label_name: label.name)
end end
it 'has valid color' do
expect(result.css('a span').first.attr('style'))
.to match /background-color: #00ff00/
end
it 'has valid color' do it 'contains cross project content' do
expect(result.css('a span').first.attr('style')) expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}"
.to match /background-color: #00ff00/ end
end end
it 'contains cross project content' do context 'project that does not exist referenced' do
expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}" let(:result) { reference_filter('aaa/bbb~ccc') }
it 'does not link reference' do
expect(result.to_html).to eq 'aaa/bbb~ccc'
end
end end
end end
end end
...@@ -42,7 +42,7 @@ describe Gitlab::Badge::Build do ...@@ -42,7 +42,7 @@ describe Gitlab::Badge::Build do
end end
context 'build exists' do context 'build exists' do
let(:ci_commit) { create(:ci_commit, project: project, sha: sha) } let(:ci_commit) { create(:ci_commit, project: project, sha: sha, ref: branch) }
let!(:build) { create(:ci_build, commit: ci_commit) } let!(:build) { create(:ci_build, commit: ci_commit) }
...@@ -57,7 +57,7 @@ describe Gitlab::Badge::Build do ...@@ -57,7 +57,7 @@ describe Gitlab::Badge::Build do
describe '#data' do describe '#data' do
let(:data) { badge.data } let(:data) { badge.data }
it 'contains infromation about success' do it 'contains information about success' do
expect(status_node(data, 'success')).to be_truthy expect(status_node(data, 'success')).to be_truthy
end end
end end
...@@ -74,7 +74,7 @@ describe Gitlab::Badge::Build do ...@@ -74,7 +74,7 @@ describe Gitlab::Badge::Build do
describe '#data' do describe '#data' do
let(:data) { badge.data } let(:data) { badge.data }
it 'contains infromation about failure' do it 'contains information about failure' do
expect(status_node(data, 'failed')).to be_truthy expect(status_node(data, 'failed')).to be_truthy
end end
end end
......
...@@ -27,6 +27,8 @@ describe Ci::Commit, models: true do ...@@ -27,6 +27,8 @@ describe Ci::Commit, models: true do
it { is_expected.to have_many(:trigger_requests) } it { is_expected.to have_many(:trigger_requests) }
it { is_expected.to have_many(:builds) } it { is_expected.to have_many(:builds) }
it { is_expected.to validate_presence_of :sha } it { is_expected.to validate_presence_of :sha }
it { is_expected.to validate_presence_of :status }
it { is_expected.to delegate_method(:stages).to(:statuses) }
it { is_expected.to respond_to :git_author_name } it { is_expected.to respond_to :git_author_name }
it { is_expected.to respond_to :git_author_email } it { is_expected.to respond_to :git_author_email }
...@@ -52,57 +54,9 @@ describe Ci::Commit, models: true do ...@@ -52,57 +54,9 @@ describe Ci::Commit, models: true do
it { expect(commit.sha).to start_with(subject) } it { expect(commit.sha).to start_with(subject) }
end end
describe :stage do
subject { commit.stage }
before do
@second = FactoryGirl.create :commit_status, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: 'pending'
@first = FactoryGirl.create :commit_status, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: 'pending'
end
it 'returns first running stage' do
is_expected.to eq('test')
end
context 'first build succeeded' do
before do
@first.success
end
it 'returns last running stage' do
is_expected.to eq('deploy')
end
end
context 'all builds succeeded' do
before do
@first.success
@second.success
end
it 'returns nil' do
is_expected.to be_nil
end
end
end
describe :create_next_builds do describe :create_next_builds do
end end
describe :refs do
subject { commit.refs }
before do
FactoryGirl.create :commit_status, commit: commit, name: 'deploy'
FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'develop'
FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'master'
end
it 'returns all refs' do
is_expected.to contain_exactly('master', 'develop', nil)
end
end
describe :retried do describe :retried do
subject { commit.retried } subject { commit.retried }
...@@ -117,10 +71,10 @@ describe Ci::Commit, models: true do ...@@ -117,10 +71,10 @@ describe Ci::Commit, models: true do
end end
describe :create_builds do describe :create_builds do
let!(:commit) { FactoryGirl.create :ci_commit, project: project } let!(:commit) { FactoryGirl.create :ci_commit, project: project, ref: 'master', tag: false }
def create_builds(trigger_request = nil) def create_builds(trigger_request = nil)
commit.create_builds('master', false, nil, trigger_request) commit.create_builds(nil, trigger_request)
end end
def create_next_builds def create_next_builds
...@@ -143,67 +97,6 @@ describe Ci::Commit, models: true do ...@@ -143,67 +97,6 @@ describe Ci::Commit, models: true do
expect(create_next_builds).to be_falsey expect(create_next_builds).to be_falsey
end end
context 'for different ref' do
def create_develop_builds
commit.create_builds('develop', false, nil, nil)
end
it 'creates builds' do
expect(create_builds).to be_truthy
commit.builds.update_all(status: "success")
expect(commit.builds.count(:all)).to eq(2)
expect(create_develop_builds).to be_truthy
commit.builds.update_all(status: "success")
expect(commit.builds.count(:all)).to eq(4)
expect(commit.refs.size).to eq(2)
expect(commit.builds.pluck(:name).uniq.size).to eq(2)
end
end
context 'for build triggers' do
let(:trigger) { FactoryGirl.create :ci_trigger, project: project }
let(:trigger_request) { FactoryGirl.create :ci_trigger_request, commit: commit, trigger: trigger }
it 'creates builds' do
expect(create_builds(trigger_request)).to be_truthy
expect(commit.builds.count(:all)).to eq(2)
end
it 'rebuilds commit' do
expect(create_builds).to be_truthy
expect(commit.builds.count(:all)).to eq(2)
expect(create_builds(trigger_request)).to be_truthy
expect(commit.builds.count(:all)).to eq(4)
end
it 'creates next builds' do
expect(create_builds(trigger_request)).to be_truthy
expect(commit.builds.count(:all)).to eq(2)
commit.builds.update_all(status: "success")
expect(create_next_builds).to be_truthy
expect(commit.builds.count(:all)).to eq(4)
end
context 'for [ci skip]' do
before do
allow(commit).to receive(:git_commit_message) { 'message [ci skip]' }
end
it 'rebuilds commit' do
expect(commit.status).to eq('skipped')
expect(create_builds).to be_truthy
# since everything in Ci::Commit is cached we need to fetch a new object
new_commit = Ci::Commit.find_by_id(commit.id)
expect(new_commit.status).to eq('pending')
end
end
end
context 'custom stage with first job allowed to fail' do context 'custom stage with first job allowed to fail' do
let(:yaml) do let(:yaml) do
{ {
...@@ -284,6 +177,7 @@ describe Ci::Commit, models: true do ...@@ -284,6 +177,7 @@ describe Ci::Commit, models: true do
commit.builds.running_or_pending.each(&:success) commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success') expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
commit.reload
expect(commit.status).to eq('success') expect(commit.status).to eq('success')
end end
...@@ -306,6 +200,7 @@ describe Ci::Commit, models: true do ...@@ -306,6 +200,7 @@ describe Ci::Commit, models: true do
commit.builds.running_or_pending.each(&:success) commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success') expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
commit.reload
expect(commit.status).to eq('failed') expect(commit.status).to eq('failed')
end end
...@@ -329,6 +224,7 @@ describe Ci::Commit, models: true do ...@@ -329,6 +224,7 @@ describe Ci::Commit, models: true do
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup') expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success') expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
commit.reload
expect(commit.status).to eq('failed') expect(commit.status).to eq('failed')
end end
...@@ -351,6 +247,7 @@ describe Ci::Commit, models: true do ...@@ -351,6 +247,7 @@ describe Ci::Commit, models: true do
commit.builds.running_or_pending.each(&:success) commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success') expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
commit.reload
expect(commit.status).to eq('failed') expect(commit.status).to eq('failed')
end end
end end
...@@ -402,4 +299,98 @@ describe Ci::Commit, models: true do ...@@ -402,4 +299,98 @@ describe Ci::Commit, models: true do
expect(commit.coverage).to be_nil expect(commit.coverage).to be_nil
end end
end end
describe '#retryable?' do
subject { commit.retryable? }
context 'no failed builds' do
before do
FactoryGirl.create :ci_build, name: "rspec", commit: commit, status: 'success'
end
it 'be not retryable' do
is_expected.to be_falsey
end
end
context 'with failed builds' do
before do
FactoryGirl.create :ci_build, name: "rspec", commit: commit, status: 'running'
FactoryGirl.create :ci_build, name: "rubocop", commit: commit, status: 'failed'
end
it 'be retryable' do
is_expected.to be_truthy
end
end
end
describe '#stages' do
let(:commit2) { FactoryGirl.create :ci_commit, project: project }
subject { CommitStatus.where(commit: [commit, commit2]).stages }
before do
FactoryGirl.create :ci_build, commit: commit2, stage: 'test', stage_idx: 1
FactoryGirl.create :ci_build, commit: commit, stage: 'build', stage_idx: 0
end
it 'return all stages' do
is_expected.to eq(%w(build test))
end
end
describe '#update_state' do
it 'execute update_state after touching object' do
expect(commit).to receive(:update_state).and_return(true)
commit.touch
end
context 'dependent objects' do
let(:commit_status) { build :commit_status, commit: commit }
it 'execute update_state after saving dependent object' do
expect(commit).to receive(:update_state).and_return(true)
commit_status.save
end
end
context 'update state' do
let(:current) { Time.now.change(usec: 0) }
let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: current - 120, finished_at: current - 60 }
before do
build
end
[:status, :started_at, :finished_at, :duration].each do |param|
it "update #{param}" do
expect(commit.send(param)).to eq(build.send(param))
end
end
end
end
describe '#branch?' do
subject { commit.branch? }
context 'is not a tag' do
before do
commit.tag = false
end
it 'return true when tag is set to false' do
is_expected.to be_truthy
end
end
context 'is not a tag' do
before do
commit.tag = true
end
it 'return false when tag is set to true' do
is_expected.to be_falsey
end
end
end
end end
...@@ -163,4 +163,12 @@ eos ...@@ -163,4 +163,12 @@ eos
it { expect(commit.reverts_commit?(another_commit)).to be_truthy } it { expect(commit.reverts_commit?(another_commit)).to be_truthy }
end end
end end
describe '#ci_commits' do
# TODO: kamil
end
describe '#status' do
# TODO: kamil
end
end end
...@@ -163,37 +163,73 @@ describe CommitStatus, models: true do ...@@ -163,37 +163,73 @@ describe CommitStatus, models: true do
end end
it 'return unique statuses' do it 'return unique statuses' do
is_expected.to eq([@commit2, @commit3, @commit4, @commit5]) is_expected.to eq([@commit4, @commit5])
end end
end end
describe :for_ref do describe :running_or_pending do
subject { CommitStatus.for_ref('bb').order(:id) } subject { CommitStatus.running_or_pending.order(:id) }
before do before do
@commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running' @commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running'
@commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending' @commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending'
@commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success' @commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success'
@commit4 = FactoryGirl.create :commit_status, commit: commit, name: 'dd', ref: nil, status: 'failed'
@commit5 = FactoryGirl.create :commit_status, commit: commit, name: 'ee', ref: nil, status: 'canceled'
end end
it 'return statuses with equal and nil ref set' do it 'return statuses that are running or pending' do
is_expected.to eq([@commit1]) is_expected.to eq([@commit1, @commit2])
end end
end end
describe :running_or_pending do describe '#before_sha' do
subject { CommitStatus.running_or_pending.order(:id) } subject { commit_status.before_sha }
context 'when no before_sha is set for ci::commit' do
before { commit.before_sha = nil }
it 'return blank sha' do
is_expected.to eq(Gitlab::Git::BLANK_SHA)
end
end
context 'for before_sha set for ci::commit' do
let(:value) { '1234' }
before { commit.before_sha = value }
it 'return the set value' do
is_expected.to eq(value)
end
end
end
describe '#stages' do
before do before do
@commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running' FactoryGirl.create :commit_status, commit: commit, stage: 'build', stage_idx: 0, status: 'success'
@commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending' FactoryGirl.create :commit_status, commit: commit, stage: 'build', stage_idx: 0, status: 'failed'
@commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success' FactoryGirl.create :commit_status, commit: commit, stage: 'deploy', stage_idx: 2, status: 'running'
@commit4 = FactoryGirl.create :commit_status, commit: commit, name: 'dd', ref: nil, status: 'failed' FactoryGirl.create :commit_status, commit: commit, stage: 'test', stage_idx: 1, status: 'success'
@commit5 = FactoryGirl.create :commit_status, commit: commit, name: 'ee', ref: nil, status: 'canceled'
end end
it 'return statuses that are running or pending' do context 'stages list' do
is_expected.to eq([@commit1, @commit2]) subject { CommitStatus.where(commit: commit).stages }
it 'return ordered list of stages' do
is_expected.to eq(%w(build test deploy))
end
end
context 'stages with statuses' do
subject { CommitStatus.where(commit: commit).stages_status }
it 'return list of stages with statuses' do
is_expected.to eq({
'build' => 'failed',
'test' => 'success',
'deploy' => 'running'
})
end
end end
end end
end end
...@@ -212,4 +212,34 @@ describe Issue, "Issuable" do ...@@ -212,4 +212,34 @@ describe Issue, "Issuable" do
expect(issue.downvotes).to eq(1) expect(issue.downvotes).to eq(1)
end end
end end
describe ".with_label" do
let(:project) { create(:project, :public) }
let(:bug) { create(:label, project: project, title: 'bug') }
let(:feature) { create(:label, project: project, title: 'feature') }
let(:enhancement) { create(:label, project: project, title: 'enhancement') }
let(:issue1) { create(:issue, title: "Bugfix1", project: project) }
let(:issue2) { create(:issue, title: "Bugfix2", project: project) }
let(:issue3) { create(:issue, title: "Feature1", project: project) }
before(:each) do
issue1.labels << bug
issue1.labels << feature
issue2.labels << bug
issue2.labels << enhancement
issue3.labels << feature
end
it 'finds the correct issue containing just enhancement label' do
expect(Issue.with_label(enhancement.title)).to match_array([issue2])
end
it 'finds the correct issues containing the same label' do
expect(Issue.with_label(bug.title)).to match_array([issue1, issue2])
end
it 'finds the correct issues containing only both labels' do
expect(Issue.with_label([bug.title, enhancement.title])).to match_array([issue2])
end
end
end end
require 'spec_helper' require 'spec_helper'
describe Ci::Status do describe Statuseable do
describe '.get_status' do before do
subject { described_class.get_status(statuses) } @object = Object.new
@object.extend(Statuseable::ClassMethods)
end
describe '.status' do
before do
allow(@object).to receive(:all).and_return(CommitStatus.where(id: statuses))
end
subject { @object.status }
shared_examples 'build status summary' do shared_examples 'build status summary' do
context 'all successful' do context 'all successful' do
......
...@@ -404,12 +404,12 @@ describe MergeRequest, models: true do ...@@ -404,12 +404,12 @@ describe MergeRequest, models: true do
describe 'when the source project exists' do describe 'when the source project exists' do
it 'returns the latest commit' do it 'returns the latest commit' do
commit = double(:commit, id: '123abc') commit = double(:commit, id: '123abc')
ci_commit = double(:ci_commit) ci_commit = double(:ci_commit, ref: 'master')
allow(subject).to receive(:last_commit).and_return(commit) allow(subject).to receive(:last_commit).and_return(commit)
expect(subject.source_project).to receive(:ci_commit). expect(subject.source_project).to receive(:ci_commit).
with('123abc'). with('123abc', 'master').
and_return(ci_commit) and_return(ci_commit)
expect(subject.ci_commit).to eq(ci_commit) expect(subject.ci_commit).to eq(ci_commit)
......
...@@ -441,9 +441,22 @@ describe Project, models: true do ...@@ -441,9 +441,22 @@ describe Project, models: true do
describe :ci_commit do describe :ci_commit do
let(:project) { create :project } let(:project) { create :project }
let(:commit) { create :ci_commit, project: project } let(:commit) { create :ci_commit, project: project, ref: 'master' }
it { expect(project.ci_commit(commit.sha)).to eq(commit) } subject { project.ci_commit(commit.sha, 'master') }
it { is_expected.to eq(commit) }
context 'return latest' do
let(:commit2) { create :ci_commit, project: project, ref: 'master' }
before do
commit
commit2
end
it { is_expected.to eq(commit2) }
end
end end
describe :builds_enabled do describe :builds_enabled do
......
...@@ -59,7 +59,7 @@ describe API::API, api: true do ...@@ -59,7 +59,7 @@ describe API::API, api: true do
describe 'GET /projects/:id/repository/commits/:sha/builds' do describe 'GET /projects/:id/repository/commits/:sha/builds' do
before do before do
project.ensure_ci_commit(commit.sha) project.ensure_ci_commit(commit.sha, 'master')
get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds", api_user) get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds", api_user)
end end
......
...@@ -16,7 +16,8 @@ describe API::CommitStatus, api: true do ...@@ -16,7 +16,8 @@ describe API::CommitStatus, api: true do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" } let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
context 'ci commit exists' do context 'ci commit exists' do
let!(:ci_commit) { project.ensure_ci_commit(commit.id) } let!(:master) { project.ci_commits.create(sha: commit.id, ref: 'master') }
let!(:develop) { project.ci_commits.create(sha: commit.id, ref: 'develop') }
it_behaves_like 'a paginated resources' do it_behaves_like 'a paginated resources' do
let(:request) { get api(get_url, reporter) } let(:request) { get api(get_url, reporter) }
...@@ -25,16 +26,16 @@ describe API::CommitStatus, api: true do ...@@ -25,16 +26,16 @@ describe API::CommitStatus, api: true do
context "reporter user" do context "reporter user" do
let(:statuses_id) { json_response.map { |status| status['id'] } } let(:statuses_id) { json_response.map { |status| status['id'] } }
def create_status(opts = {}) def create_status(commit, opts = {})
create(:commit_status, { commit: ci_commit }.merge(opts)) create(:commit_status, { commit: commit, ref: commit.ref }.merge(opts))
end end
let!(:status1) { create_status(status: 'running') } let!(:status1) { create_status(master, status: 'running') }
let!(:status2) { create_status(name: 'coverage', status: 'pending') } let!(:status2) { create_status(master, name: 'coverage', status: 'pending') }
let!(:status3) { create_status(ref: 'develop', status: 'running', allow_failure: true) } let!(:status3) { create_status(develop, status: 'running', allow_failure: true) }
let!(:status4) { create_status(name: 'coverage', status: 'success') } let!(:status4) { create_status(master, name: 'coverage', status: 'success') }
let!(:status5) { create_status(name: 'coverage', ref: 'develop', status: 'success') } let!(:status5) { create_status(develop, name: 'coverage', status: 'success') }
let!(:status6) { create_status(status: 'success') } let!(:status6) { create_status(master, status: 'success') }
context 'latest commit statuses' do context 'latest commit statuses' do
before { get api(get_url, reporter) } before { get api(get_url, reporter) }
......
...@@ -48,14 +48,14 @@ describe API::API, api: true do ...@@ -48,14 +48,14 @@ describe API::API, api: true do
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
it "should return not_found for CI status" do it "should return nil for commit without CI" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response['status']).to eq('not_found') expect(json_response['status']).to be_nil
end end
it "should return status for CI" do it "should return status for CI" do
ci_commit = project.ensure_ci_commit(project.repository.commit.sha) ci_commit = project.ensure_ci_commit(project.repository.commit.sha, 'master')
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response['status']).to eq(ci_commit.status) expect(json_response['status']).to eq(ci_commit.status)
......
...@@ -20,8 +20,8 @@ describe Ci::API::API do ...@@ -20,8 +20,8 @@ describe Ci::API::API do
describe "POST /builds/register" do describe "POST /builds/register" do
it "should start a build" do it "should start a build" do
commit = FactoryGirl.create(:ci_commit, project: project) commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
commit.create_builds('master', false, nil) commit.create_builds(nil)
build = commit.builds.first build = commit.builds.first
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
...@@ -56,8 +56,8 @@ describe Ci::API::API do ...@@ -56,8 +56,8 @@ describe Ci::API::API do
end end
it "returns options" do it "returns options" do
commit = FactoryGirl.create(:ci_commit, project: project) commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
commit.create_builds('master', false, nil) commit.create_builds(nil)
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
...@@ -66,8 +66,8 @@ describe Ci::API::API do ...@@ -66,8 +66,8 @@ describe Ci::API::API do
end end
it "returns variables" do it "returns variables" do
commit = FactoryGirl.create(:ci_commit, project: project) commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
commit.create_builds('master', false, nil) commit.create_builds(nil)
project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value") project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
...@@ -83,10 +83,10 @@ describe Ci::API::API do ...@@ -83,10 +83,10 @@ describe Ci::API::API do
it "returns variables for triggers" do it "returns variables for triggers" do
trigger = FactoryGirl.create(:ci_trigger, project: project) trigger = FactoryGirl.create(:ci_trigger, project: project)
commit = FactoryGirl.create(:ci_commit, project: project) commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger) trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger)
commit.create_builds('master', false, nil, trigger_request) commit.create_builds(nil, trigger_request)
project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value") project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
...@@ -103,8 +103,8 @@ describe Ci::API::API do ...@@ -103,8 +103,8 @@ describe Ci::API::API do
end end
it "returns dependent builds" do it "returns dependent builds" do
commit = FactoryGirl.create(:ci_commit, project: project) commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
commit.create_builds('master', false, nil, nil) commit.create_builds(nil, nil)
commit.builds.where(stage: 'test').each(&:success) commit.builds.where(stage: 'test').each(&:success)
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
......
require 'spec_helper' require 'spec_helper'
describe Ci::CreateBuildsService, services: true do describe Ci::CreateBuildsService, services: true do
let(:commit) { create(:ci_commit) } let(:commit) { create(:ci_commit, ref: 'master') }
let(:user) { create(:user) } let(:user) { create(:user) }
describe '#execute' do describe '#execute' do
...@@ -9,7 +9,7 @@ describe Ci::CreateBuildsService, services: true do ...@@ -9,7 +9,7 @@ describe Ci::CreateBuildsService, services: true do
# #
subject do subject do
described_class.new.execute(commit, 'test', 'master', nil, user, nil, status) described_class.new(commit).execute(commit, nil, user, status)
end end
context 'next builds available' do context 'next builds available' do
......
...@@ -5,7 +5,7 @@ module Ci ...@@ -5,7 +5,7 @@ module Ci
let(:service) { ImageForBuildService.new } let(:service) { ImageForBuildService.new }
let(:project) { FactoryGirl.create(:empty_project) } let(:project) { FactoryGirl.create(:empty_project) }
let(:commit_sha) { '01234567890123456789' } let(:commit_sha) { '01234567890123456789' }
let(:commit) { project.ensure_ci_commit(commit_sha) } let(:commit) { project.ensure_ci_commit(commit_sha, 'master') }
let(:build) { FactoryGirl.create(:ci_build, commit: commit) } let(:build) { FactoryGirl.create(:ci_build, commit: commit) }
describe :execute do describe :execute do
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment