Commit d95c0f5f authored by Robert Speicher's avatar Robert Speicher

Merge remote-tracking branch 'ce/master'

parents f9068a84 c4b35a62
......@@ -153,11 +153,10 @@ bundler:audit:
stage: test
script:
- "bundle exec bundle-audit update"
- "bundle exec bundle-audit check"
- "bundle exec bundle-audit check --ignore OSVDB-115941"
tags:
- ruby
- mysql
allow_failure: true
# Ruby 2.2 jobs
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.6.0 (unreleased)
- Bump gitlab_git to 9.0.3 (Stan Hu)
- Support Golang subpackage fetching (Stan Hu)
- Bump Capybara gem to 2.6.2 (Stan Hu)
- Contributions to forked projects are included in calendar
- Improve the formatting for the user page bio (Connor Shea)
- Removed the default password from the initial admin account created during
......@@ -18,10 +20,14 @@ v 8.6.0 (unreleased)
- Return empty array instead of 404 when commit has no statuses in commit status API
- Decrease the font size and the padding of the `.anchor` icons used in the README (Roberto Dip)
- Rewrite logo to simplify SVG code (Sean Lang)
- Allow to use YAML anchors when parsing the `.gitlab-ci.yml` (Pascal Bach)
- Ignore jobs that start with `.` (hidden jobs)
- Allow to pass name of created artifacts archive in `.gitlab-ci.yml`
- Refactor and greatly improve search performance
- Add support for cross-project label references
- Update documentation to reflect Guest role not being enforced on internal projects
- Allow search for logged out users
- Allow to define on which builds the current one depends on
- Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio)
- Don't show Issues/MRs from archived projects in Groups view
- Increase the notes polling timeout over time (Roberto Dip)
......
......@@ -283,7 +283,7 @@ group :development, :test do
# Generate Fake data
gem 'ffaker', '~> 2.0.0'
gem 'capybara', '~> 2.4.0'
gem 'capybara', '~> 2.6.2'
gem 'capybara-screenshot', '~> 1.0.0'
gem 'poltergeist', '~> 1.9.0'
......
......@@ -108,7 +108,8 @@ GEM
thor (~> 0.18)
byebug (8.2.1)
cal-heatmap-rails (3.5.1)
capybara (2.4.4)
capybara (2.6.2)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
......@@ -380,7 +381,7 @@ GEM
gitlab-license (0.0.4)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
gitlab_git (9.0.1)
gitlab_git (9.0.3)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
......@@ -928,7 +929,7 @@ DEPENDENCIES
bundler-audit
byebug
cal-heatmap-rails (~> 3.5.0)
capybara (~> 2.4.0)
capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.10.0)
charlock_holmes (~> 0.7.3)
......
......@@ -111,6 +111,8 @@ window.onload = ->
setTimeout shiftWindow, 100
$ ->
bootstrapBreakpoint = bp.getBreakpointSize()
$(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
# Click a .js-select-on-focus field, select the contents
......@@ -260,35 +262,14 @@ $ ->
$('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' })
bootstrapBreakpoint = undefined;
checkBootstrapBreakpoints = ->
if $('.device-xs').is(':visible')
bootstrapBreakpoint = "xs"
else if $('.device-sm').is(':visible')
bootstrapBreakpoint = "sm"
else if $('.device-md').is(':visible')
bootstrapBreakpoint = "md"
else if $('.device-lg').is(':visible')
bootstrapBreakpoint = "lg"
setBootstrapBreakpoints = ->
if $('.device-xs').length
return
$("body")
.append('<div class="device-xs visible-xs"></div>'+
'<div class="device-sm visible-sm"></div>'+
'<div class="device-md visible-md"></div>'+
'<div class="device-lg visible-lg"></div>')
checkBootstrapBreakpoints()
fitSidebarForSize = ->
oldBootstrapBreakpoint = bootstrapBreakpoint
checkBootstrapBreakpoints()
bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint != oldBootstrapBreakpoint
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
checkInitialSidebarSize = ->
bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint is "xs" or "sm"
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
......@@ -297,6 +278,5 @@ $ ->
.on "resize", (e) ->
fitSidebarForSize()
setBootstrapBreakpoints()
checkInitialSidebarSize()
new Aside()
class @Breakpoints
instance = null;
class BreakpointInstance
BREAKPOINTS = ["xs", "sm", "md", "lg"]
constructor: ->
@setup()
setup: ->
allDeviceSelector = BREAKPOINTS.map (breakpoint) ->
".device-#{breakpoint}"
return if $(allDeviceSelector.join(",")).length
# Create all the elements
els = $.map BREAKPOINTS, (breakpoint) ->
"<div class='device-#{breakpoint} visible-#{breakpoint}'></div>"
$("body").append els.join('')
getBreakpointSize: ->
allDeviceSelector = BREAKPOINTS.map (breakpoint) ->
".device-#{breakpoint}"
$visibleDevice = $(allDeviceSelector.join(",")).filter(":visible")
return $visibleDevice.attr("class").split("visible-")[1]
@get: ->
return instance ?= new BreakpointInstance
$ =>
@bp = Breakpoints.get()
......@@ -251,7 +251,7 @@ class GitLabDropdown
# Toggle active class for the tick mark
el.toggleClass "is-active"
if value
if value?
if !field.length
# Create hidden input for form
input = "<input type='hidden' name='#{fieldName}' />"
......
$(document).on("click", '.toggle-nav-collapse', (e) ->
e.preventDefault()
collapsed = 'page-sidebar-collapsed'
expanded = 'page-sidebar-expanded'
collapsed = 'page-sidebar-collapsed'
expanded = 'page-sidebar-expanded'
toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
$('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded")
......@@ -14,4 +13,15 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
niceScrollBars.updateScrollBar();
), 300
$(document).on("click", '.toggle-nav-collapse', (e) ->
e.preventDefault()
toggleSidebar()
)
$ ->
size = bp.getBreakpointSize()
if size is "xs" or size is "sm"
if $('.page-with-sidebar').hasClass(expanded)
toggleSidebar()
......@@ -141,22 +141,18 @@ header {
margin-left: $sidebar_collapsed_width;
}
@media (max-width: $screen-md-max) {
.header-collapsed {
margin-left: $sidebar_collapsed_width;
}
.header-expanded {
margin-left: $sidebar_width;
}
}
.header-collapsed {
margin-left: $sidebar_collapsed_width;
@media(min-width: $screen-md-max) {
.header-collapsed {
@media (min-width: $screen-md-min) {
@include collapsed-header;
}
}
.header-expanded {
margin-left: $sidebar_collapsed_width;
.header-expanded {
@media (min-width: $screen-md-min) {
margin-left: $sidebar_width;
}
}
......
......@@ -34,12 +34,12 @@
@media (min-width: $screen-sm-min) {
padding-right: $gutter_width;
}
}
}
.sidebar-wrapper {
z-index: 99;
z-index: 999;
background: $background-color;
}
......@@ -203,7 +203,11 @@
}
@mixin expanded-sidebar {
padding-left: $sidebar_width;
padding-left: $sidebar_collapsed_width;
@media (min-width: $screen-md-min) {
padding-left: $sidebar_width;
}
&.right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
......
......@@ -141,7 +141,7 @@ class GroupsController < Groups::ApplicationController
end
def group_params
params.require(:group).permit(:name, :description, :path, :avatar, :membership_lock, :share_with_group_lock, :public)
params.require(:group).permit(:name, :description, :path, :avatar, :public, :share_with_group_lock, :membership_lock)
end
def load_events
......
......@@ -37,8 +37,6 @@
module Ci
class Build < CommitStatus
include Gitlab::Application.routes.url_helpers
LAZY_ATTRIBUTES = ['trace']
belongs_to :runner, class_name: 'Ci::Runner'
......@@ -128,7 +126,7 @@ module Ci
end
def retried?
!self.commit.latest_builds_for_ref(self.ref).include?(self)
!self.commit.latest_statuses_for_ref(self.ref).include?(self)
end
def depends_on_builds
......@@ -309,22 +307,6 @@ module Ci
project.valid_runners_token? token
end
def target_url
namespace_project_build_url(project.namespace, project, self)
end
def cancel_url
if active?
cancel_namespace_project_build_path(project.namespace, project, self)
end
end
def retry_url
if retryable?
retry_namespace_project_build_path(project.namespace, project, self)
end
end
def can_be_served?(runner)
(tag_list - runner.tag_list).empty?
end
......@@ -333,7 +315,7 @@ module Ci
project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
end
def show_warning?
def stuck?
pending? && !any_runners_online?
end
......@@ -349,18 +331,6 @@ module Ci
artifacts_file.exists?
end
def artifacts_download_url
if artifacts?
download_namespace_project_build_artifacts_path(project.namespace, project, self)
end
end
def artifacts_browse_url
if artifacts_metadata?
browse_namespace_project_build_artifacts_path(project.namespace, project, self)
end
end
def artifacts_metadata?
artifacts? && artifacts_metadata.exists?
end
......
......@@ -25,8 +25,6 @@ module Ci
has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
scope :ordered, -> { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }
validates_presence_of :sha
validate :valid_commit_sha
......@@ -42,16 +40,6 @@ module Ci
project.id
end
def last_build
builds.order(:id).last
end
def retry
latest_builds.each do |build|
Ci::Build.retry(build)
end
end
def valid_commit_sha
if self.sha == Gitlab::Git::BLANK_SHA
self.errors.add(:sha, " cant be 00000000 (branch removal)")
......@@ -121,12 +109,14 @@ module Ci
@latest_statuses ||= statuses.latest.to_a
end
def latest_builds
@latest_builds ||= builds.latest.to_a
def latest_statuses_for_ref(ref)
latest_statuses.select { |status| status.ref == ref }
end
def latest_builds_for_ref(ref)
latest_builds.select { |build| build.ref == ref }
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
......@@ -170,7 +160,7 @@ module Ci
end
def duration
duration_array = latest_statuses.map(&:duration).compact
duration_array = statuses.map(&:duration).compact
duration_array.reduce(:+).to_i
end
......@@ -183,16 +173,12 @@ module Ci
end
def coverage
coverage_array = latest_builds.map(&:coverage).compact
coverage_array = latest_statuses.map(&:coverage).compact
if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
end
def matrix_for_ref?(ref)
latest_builds_for_ref(ref).size > 1
end
def config_processor
return nil unless ci_yaml_file
@config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
......@@ -218,10 +204,6 @@ module Ci
git_commit_message =~ /(\[ci skip\])/ if git_commit_message
end
def update_committed!
update!(committed_at: DateTime.now)
end
private
def save_yaml_error(error)
......
......@@ -125,23 +125,7 @@ class CommitStatus < ActiveRecord::Base
end
end
def cancel_url
nil
end
def retry_url
nil
end
def show_warning?
def stuck?
false
end
def artifacts_download_url
nil
end
def artifacts_browse_url
nil
end
end
......@@ -179,26 +179,29 @@ class Note < ActiveRecord::Base
Note.where(noteable_id: noteable_id, noteable_type: noteable_type, line_code: line_code).last.try(:diff)
end
# Check if such line of code exists in merge request diff
# If exists - its active discussion
# If not - its outdated diff
# Check if this note is part of an "active" discussion
#
# This will always return true for anything except MergeRequest noteables,
# which have special logic.
#
# If the note's current diff cannot be matched in the MergeRequest's current
# diff, it's considered inactive.
def active?
return true unless self.diff
return false unless noteable
return @active if defined?(@active)
diffs = noteable.diffs(Commit.max_diff_options)
notable_diff = diffs.find { |d| d.new_path == self.diff.new_path }
noteable_diff = find_noteable_diff
return @active = false if notable_diff.nil?
if noteable_diff
parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line)
parsed_lines = Gitlab::Diff::Parser.new.parse(notable_diff.diff.each_line)
# We cannot use ||= because @active may be false
@active = parsed_lines.any? { |line_obj| line_obj.text == diff_line }
end
@active = parsed_lines.any? { |line_obj| line_obj.text == diff_line }
else
@active = false
end
def outdated?
!active?
@active
end
def diff_file_index
......@@ -386,6 +389,12 @@ class Note < ActiveRecord::Base
self.line_code = nil if self.line_code.blank?
end
# Find the diff on noteable that matches our own
def find_noteable_diff
diffs = noteable.diffs(Commit.max_diff_options)
diffs.find { |d| d.new_path == self.diff.new_path }
end
def awards_supported?
(for_issue? || for_merge_request?) && !for_diff_line?
end
......
......@@ -3,7 +3,7 @@ module Ci
def execute(project, opts)
sha = opts[:sha] || ref_sha(project, opts[:ref])
commit = project.ci_commits.ordered.find_by(sha: sha)
commit = project.ci_commits.find_by(sha: sha)
image_name = image_for_commit(commit)
image_path = Rails.root.join('public/ci', image_name)
......
......@@ -35,7 +35,6 @@ class CreateCommitBuildsService
unless commit.skip_ci?
# Create builds for commit
tag = Gitlab::Git.tag_ref?(origin_ref)
commit.update_committed!
commit.create_builds(ref, tag, user)
end
......
......@@ -4,13 +4,13 @@
= ci_status_with_icon(build.status)
%td.build-link
- if can?(current_user, :read_build, project) && build.target_url
= link_to build.target_url do
- if can?(current_user, :read_build, build.project)
= link_to namespace_project_build_url(build.project.namespace, build.project, build) do
%strong Build ##{build.id}
- else
%strong Build ##{build.id}
- if build.show_warning?
- if build.stuck?
%i.fa.fa-warning.text-warning
%td
......@@ -18,11 +18,11 @@
= link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project), class: "monospace"
%td
= link_to build.short_sha, namespace_project_commit_path(project.namespace, 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 build.ref
= link_to build.ref, namespace_project_commits_path(project.namespace, project, build.ref)
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
- else
.light none
......@@ -61,13 +61,12 @@
%td
.pull-right
- if can?(current_user, :read_build, project) && build.artifacts?
= link_to build.artifacts_download_url, title: 'Download artifacts' do
= link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts' do
%i.fa.fa-download
- if can?(current_user, :update_build, build.project)
- if build.active?
- if build.cancel_url
= link_to build.cancel_url, method: :post, title: 'Cancel' do
%i.fa.fa-remove.cred
- elsif defined?(allow_retry) && allow_retry && build.retry_url
= link_to build.retry_url, method: :post, title: 'Retry' do
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel' do
%i.fa.fa-remove.cred
- elsif defined?(allow_retry) && allow_retry && build.retryable?
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry' do
%i.fa.fa-repeat
%tr.build
%td.status
= ci_status_with_icon(commit.status)
- if commit.running?
&middot;
= commit.stage
%td.build-link
= link_to ci_status_path(commit) do
%strong #{commit.short_sha}
%td.build-message
%span= truncate_first_line(commit.git_commit_message)
%td.build-branch
- unless @ref
%span
- commit.refs.each do |ref|
= link_to truncate(ref, length: 25), ci_project_path(@project, ref: ref)
%td.duration
- if commit.duration > 0
#{time_interval_in_words commit.duration}
%td.timestamp
- if commit.finished_at
%span #{time_ago_in_words commit.finished_at} ago
- if commit.coverage
%td.coverage
#{commit.coverage}%
......@@ -45,6 +45,9 @@
.tab-pane#shared
= render "shared_projects", projects: @shared_projects
.tab-pane#shared
= render "shared_projects", projects: @shared_projects
- else
%p.nav-links.no-top
No projects to show
......@@ -16,7 +16,7 @@
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= icon('home fw')
= icon('bookmark fw')
%span
Project
= nav_link(path: 'projects#activity') do
......
......@@ -55,7 +55,6 @@
%th Coverage
%th
- @builds.each do |build|
= render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, coverage: @project.build_coverage_enabled?, allow_retry: true
= render @builds, commit_sha: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
= paginate @builds, theme: 'gitlab'
......@@ -13,9 +13,10 @@
= link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request)
#up-build-trace
- if @commit.matrix_for_ref?(@build.ref)
- builds = @build.commit.matrix_builds(@build)
- if builds.size > 1
%ul.nav-links.no-top.no-bottom
- @commit.latest_builds_for_ref(@build.ref).each do |build|
- builds.each do |build|
%li{class: ('active' if build == @build) }
= link_to namespace_project_build_path(@project.namespace, @project, build) do
= ci_icon_for_status(build.status)
......@@ -44,7 +45,7 @@
.pull-right
#{time_ago_with_tooltip(@build.finished_at) if @build.finished_at}
- if @build.show_warning?
- if @build.stuck?
- unless @build.any_runners_online?
.bs-callout.bs-callout-warning
%p
......@@ -100,12 +101,12 @@
%h4.title Build artifacts
.center
.btn-group{ role: :group }
= link_to @build.artifacts_download_url, class: 'btn btn-sm btn-primary' do
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary' do
= icon('download')
Download
- if @build.artifacts_metadata?
= link_to @build.artifacts_browse_url, class: 'btn btn-sm btn-primary' do
= link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary' do
= icon('folder-open')
Browse
......@@ -115,10 +116,10 @@
- if can?(current_user, :update_build, @project)
.center
.btn-group{ role: :group }
- if @build.cancel_url
= link_to "Cancel", @build.cancel_url, class: 'btn btn-sm btn-danger', method: :post
- elsif @build.retry_url
= link_to "Retry", @build.retry_url, class: 'btn btn-sm btn-primary', method: :post
- if @build.active?
= link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-danger', method: :post
- elsif @build.retryable?
= link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary', method: :post
- if @build.erasable?
= link_to erase_namespace_project_build_path(@project.namespace, @project, @build),
......
%tr.build
%td.status
- if can?(current_user, :read_build, build)
= link_to namespace_project_build_url(build.project.namespace, build.project, build), class: "ci-status ci-#{build.status}" do
= ci_status_with_icon(build.status)
- else
= ci_status_with_icon(build.status)
%td.build-link
- if can?(current_user, :read_build, build)
= link_to namespace_project_build_url(build.project.namespace, build.project, build) do
%strong ##{build.id}
- else
%strong ##{build.id}
- if build.stuck?
%i.fa.fa-warning.text-warning
- if defined?(commit_sha) && commit_sha
%td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
%td
- if build.ref
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
- else
.light none
- if defined?(runner) && runner
%td
- if build.try(:runner)
= runner_link(build.runner)
- else
.light none
- if defined?(stage) && stage
%td
= build.stage
%td
= build.name
.pull-right
- if build.tags.any?
- build.tags.each do |tag|
%span.label.label-primary
= tag
- if build.try(:trigger_request)
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
%td.duration
- if build.duration
#{duration_in_words(build.finished_at, build.started_at)}
%td.timestamp
- if build.finished_at
%span #{time_ago_with_tooltip(build.finished_at)}
- if defined?(coverage) && coverage
%td.coverage
- if build.try(:coverage)
#{build.coverage}%
%td
.pull-right
- if can?(current_user, :read_build, build) && build.artifacts?
= link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts' do
%i.fa.fa-download
- if can?(current_user, :update_build, build)
- if build.active?
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel' do
%i.fa.fa-remove.cred
- elsif defined?(allow_retry) && allow_retry && build.retryable?
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry' do
%i.fa.fa-repeat
......@@ -43,8 +43,8 @@
%th Coverage
%th
- @ci_commit.refs.each do |ref|
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered,
locals: { coverage: @ci_commit.project.build_coverage_enabled?, stage: true, allow_retry: true }
- 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
......@@ -64,5 +64,4 @@
- if @ci_commit.project.build_coverage_enabled?
%th Coverage
%th
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried,
locals: { coverage: @ci_commit.project.build_coverage_enabled?, stage: true }
= render @ci_commit.retried, coverage: @ci_commit.project.build_coverage_enabled?, stage: true
%tr.commit_status
%td.status
- if can?(current_user, :read_commit_status, commit_status) && commit_status.target_url
= link_to commit_status.target_url, class: "ci-status ci-#{commit_status.status}" do
= ci_icon_for_status(commit_status.status)
= commit_status.status
- else
= ci_status_with_icon(commit_status.status)
%td.commit_status-link
- if can?(current_user, :read_commit_status, commit_status) && commit_status.target_url
= link_to commit_status.target_url do
%strong ##{commit_status.id}
- else
%strong ##{commit_status.id}
- if commit_status.show_warning?
%i.fa.fa-warning.text-warning{data: { toggle: "tooltip" }, title: "This build is stuck, open it to know more"}
- if defined?(commit_sha) && commit_sha
%td
= link_to commit_status.short_sha, namespace_project_commit_path(commit_status.project.namespace, commit_status.project, commit_status.sha), class: "monospace"
%td
- if commit_status.ref
= link_to commit_status.ref, namespace_project_commits_path(commit_status.project.namespace, commit_status.project, commit_status.ref)
- else
.light none
- if defined?(runner) && runner
%td
- if commit_status.try(:runner)
= runner_link(commit_status.runner)
- else
.light none
- if defined?(stage) && stage
%td
= commit_status.stage
%td
= commit_status.name
.pull-right
- if commit_status.tags.any?
- commit_status.tags.each do |tag|
%span.label.label-primary
= tag
- if commit_status.try(:trigger_request)
%span.label.label-info triggered
- if commit_status.try(:allow_failure)
%span.label.label-danger allowed to fail
%td.duration
- if commit_status.duration
#{duration_in_words(commit_status.finished_at, commit_status.started_at)}
%td.timestamp
- if commit_status.finished_at
%span #{time_ago_with_tooltip(commit_status.finished_at)}
- if defined?(coverage) && coverage
%td.coverage
- if commit_status.try(:coverage)
#{commit_status.coverage}%
%td
.pull-right
- if can?(current_user, :read_commit_status, commit_status) && commit_status.artifacts_download_url
= link_to commit_status.artifacts_download_url, title: 'Download artifacts' do
%i.fa.fa-download
- if can?(current_user, :update_commit_status, commit_status)
- if commit_status.active?
- if commit_status.cancel_url
= link_to commit_status.cancel_url, method: :post, title: 'Cancel' do
%i.fa.fa-remove.cred
- elsif defined?(allow_retry) && allow_retry && commit_status.retry_url
= link_to commit_status.retry_url, method: :post, title: 'Retry' do
%i.fa.fa-repeat
%tr.generic_commit_status
%td.status
- if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url
= link_to generic_commit_status.target_url, class: "ci-status ci-#{generic_commit_status.status}" do
= ci_status_with_icon(generic_commit_status.status)
- else
= ci_status_with_icon(generic_commit_status.status)
%td.generic_commit_status-link
- if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url
= link_to generic_commit_status.target_url do
%strong ##{generic_commit_status.id}
- else
%strong ##{generic_commit_status.id}
- if defined?(commit_sha) && commit_sha
%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"
%td
- if generic_commit_status.ref
= link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
- else
.light none
- if defined?(runner) && runner
%td
- if generic_commit_status.try(:runner)
= runner_link(generic_commit_status.runner)
- else
.light none
- if defined?(stage) && stage
%td
= generic_commit_status.stage
%td
= generic_commit_status.name
.pull-right
- if generic_commit_status.tags.any?
- generic_commit_status.tags.each do |tag|
%span.label.label-primary
= tag
%td.duration
- if generic_commit_status.duration
#{duration_in_words(generic_commit_status.finished_at, generic_commit_status.started_at)}
%td.timestamp
- if generic_commit_status.finished_at
%span #{time_ago_with_tooltip(generic_commit_status.finished_at)}
- if defined?(coverage) && coverage
%td.coverage
- if generic_commit_status.try(:coverage)
#{generic_commit_status.coverage}%
%td
......@@ -106,7 +106,7 @@
%a{href: "#", data: {id: "close"}} Closed
.filter-item.inline
= dropdown_tag("Assignee", options: { toggle_class: "js-user-search", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } })
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } })
.filter-item.inline
= dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select', filter: true, dropdown_class: "dropdown-menu-selectable",
placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :js), use_id: true } })
......
......@@ -33,7 +33,6 @@ Example of response
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.802Z",
"download_url": null,
"artifacts_file": {
"filename": "artifacts.zip",
"size": 1000
......@@ -75,7 +74,6 @@ Example of response
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.727Z",
"download_url": null,
"artifacts_file": null,
"finished_at": "2015-12-24T17:54:24.921Z",
"id": 6,
......@@ -139,7 +137,6 @@ Example of response
},
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
"download_url": null,
"artifacts_file": null,
"finished_at": "2016-01-11T10:14:09.526Z",
"id": 69,
......@@ -164,7 +161,6 @@ Example of response
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.957Z",
"download_url": null,
"artifacts_file": null,
"finished_at": "2015-12-24T17:54:33.913Z",
"id": 9,
......@@ -226,7 +222,6 @@ Example of response
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.880Z",
"download_url": null,
"artifacts_file": null,
"finished_at": "2015-12-24T17:54:31.198Z",
"id": 8,
......@@ -315,7 +310,6 @@ Example of response
},
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
"download_url": null,
"artifacts_file": null,
"finished_at": "2016-01-11T10:14:09.526Z",
"id": 69,
......@@ -362,7 +356,6 @@ Example of response
},
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
"download_url": null,
"artifacts_file": null,
"finished_at": null,
"id": 69,
......
......@@ -116,7 +116,8 @@ Alias for [stages](#stages).
### variables
_**Note:** Introduced in GitLab Runner v0.5.0._
>**Note:**
Introduced in GitLab Runner v0.5.0.
GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build
environment. The variables are stored in the git repository and are meant to
......@@ -153,7 +154,8 @@ cache:
#### cache:key
_**Note:** Introduced in GitLab Runner v1.0.0._
>**Note:**
Introduced in GitLab Runner v1.0.0.
The `key` directive allows you to define the affinity of caching
between jobs, allowing to have a single cache for all jobs,
......@@ -234,13 +236,14 @@ job_name:
| Keyword | Required | Description |
|---------------|----------|-------------|
| script | yes | Defines a shell script which is executed by runner |
| stage | no (default: `test`) | Defines a build stage |
| stage | no | Defines a build stage (default: `test`) |
| type | no | Alias for `stage` |
| only | no | Defines a list of git refs for which build is created |
| except | no | Defines a list of git refs for which build is not created |
| tags | no | Defines a list of tags which are used to select runner |
| allow_failure | no | Allow build to fail. Failed build doesn't contribute to commit status |
| when | no | Define when to run build. Can be `on_success`, `on_failure` or `always` |
| dependencies | no | Define other builds that a build depends on so that you can pass artifacts between them|
| artifacts | no | Define list build artifacts |
| cache | no | Define list of files that should be cached between subsequent runs |
......@@ -393,15 +396,18 @@ The above script will:
### artifacts
_**Note:** Introduced in GitLab Runner v0.7.0 for non-Windows platforms._
_**Note:** Limited Windows support was added in GitLab Runner v.1.0.0.
Currently not all executors are supported._
_**Note:** Build artifacts are only collected for successful builds._
>**Notes:**
>
> - Introduced in GitLab Runner v0.7.0 for non-Windows platforms.
> - Limited Windows support was added in GitLab Runner v.1.0.0.
> - Currently not all executors are supported.
> - Build artifacts are only collected for successful builds.
`artifacts` is used to specify list of files and directories which should be
attached to build after success. Below are some examples.
attached to build after success. To pass artifacts between different builds,
see [dependencies](#dependencies).
Below are some examples.
Send all files in `binaries` and `.config`:
......@@ -453,9 +459,130 @@ release-job:
The artifacts will be sent to GitLab after a successful build and will
be available for download in the GitLab UI.
#### artifacts:name
>**Note:**
Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
The `name` directive allows you to define the name of the created artifacts
archive. That way, you can have a unique name of every archive which could be
useful when you'd like to download the archive from GitLab. The `artifacts:name`
variable can make use of any of the [predefined variables](../variables/README.md).
---
**Example configurations**
To create an archive with a name of the current build:
```yaml
job:
artifacts:
name: "$CI_BUILD_NAME"
```
To create an archive with a name of the current branch or tag including only
the files that are untracked by Git:
```yaml
job:
artifacts:
name: "$CI_BUILD_REF_NAME"
untracked: true
```
To create an archive with a name of the current build and the current branch or
tag including only the files that are untracked by Git:
```yaml
job:
artifacts:
name: "${CI_BUILD_NAME}_${CI_BUILD_REF_NAME}"
untracked: true
```
To create an archive with a name of the current [stage](#stages) and branch name:
```yaml
job:
artifacts:
name: "${CI_BUILD_STAGE}_${CI_BUILD_REF_NAME}"
untracked: true
```
---
If you use **Windows Batch** to run your shell scripts you need to replace
`$` with `%`:
```yaml
job:
artifacts:
name: "%CI_BUILD_STAGE%_%CI_BUILD_REF_NAME%"
untracked: true
```
### dependencies
>**Note:**
Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
This feature should be used in conjunction with [`artifacts`](#artifacts) and
allows you to define the artifacts to pass between different builds.
Note that `artifacts` from previous [stages](#stages) are passed by default.
To use this feature, define `dependencies` in context of the job and pass
a list of all previous builds from which the artifacts should be downloaded.
You can only define builds from stages that are executed before the current one.
An error will be shown if you define builds from the current stage or next ones.
---
In the following example, we define two jobs with artifacts, `build:osx` and
`build:linux`. When the `test:osx` is executed, the artifacts from `build:osx`
will be downloaded and extracted in the context of the build. The same happens
for `test:linux` and artifacts from `build:linux`.
The job `deploy` will download artifacts from all previous builds because of
the [stage](#stages) precedence:
```yaml
build:osx:
stage: build
script: make build:osx
artifacts:
paths:
- binaries/
build:linux:
stage: build
script: make build:linux
artifacts:
paths:
- binaries/
test:osx:
stage: test
script: make test:osx
dependencies:
- build:osx
test:linux:
stage: test
script: make test:linux
dependencies:
- build:linux
deploy:
stage: deploy
script: make deploy
```
### cache
_**Note:** Introduced in GitLab Runner v0.7.0._
>**Note:**
Introduced in GitLab Runner v0.7.0.
`cache` is used to specify list of files and directories which should be cached
between builds. Below are some examples:
......@@ -538,6 +665,155 @@ pages:
Read more on [GitLab Pages user documentation](../../pages/README.md).
## Hidden jobs
>**Note:**
Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
Jobs that start with a dot (`.`) will be not processed by GitLab CI. You can
use this feature to ignore jobs, or use the
[special YAML features](#special-yaml-features) and transform the hidden jobs
into templates.
In the following example, `.job_name` will be ignored:
```yaml
.job_name:
script:
- rake spec
```
## Special YAML features
It's possible to use special YAML features like anchors (`&`), aliases (`*`)
and map merging (`<<`), which will allow you to greatly reduce the complexity
of `.gitlab-ci.yml`.
Read more about the various [YAML features](https://learnxinyminutes.com/docs/yaml/).
### Anchors
>**Note:**
Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
YAML also has a handy feature called 'anchors', which let you easily duplicate
content across your document. Anchors can be used to duplicate/inherit
properties, and is a perfect example to be used with [hidden jobs](#hidden-jobs)
to provide templates for your jobs.
The following example uses anchors and map merging. It will create two jobs,
`test1` and `test2`, that will inherit the parameters of `.job_template`, each
having their own custom `script` defined:
```yaml
.job_template: &job_definition # Hidden job that defines an anchor named 'job_definition'
image: ruby:2.1
services:
- postgres
- redis
test1:
<<: *job_definition # Merge the contents of the 'job_definition' alias
script:
- test1 project
test2:
<<: *job_definition # Merge the contents of the 'job_definition' alias
script:
- test2 project
```
`&` sets up the name of the anchor (`job_definition`), `<<` means "merge the
given hash into the current one", and `*` includes the named anchor
(`job_definition` again). The expanded version looks like this:
```yaml
.job_template:
image: ruby:2.1
services:
- postgres
- redis
test1:
image: ruby:2.1
services:
- postgres
- redis
script:
- test1 project
test2:
image: ruby:2.1
services:
- postgres
- redis
script:
- test2 project
```
Let's see another one example. This time we will use anchors to define two sets
of services. This will create two jobs, `test:postgres` and `test:mysql`, that
will share the `script` directive defined in `.job_template`, and the `services`
directive defined in `.postgres_services` and `.mysql_services` respectively:
```yaml
.job_template: &job_definition
script:
- test project
.postgres_services:
services: &postgres_definition
- postgres
- ruby
.mysql_services:
services: &mysql_definition
- mysql
- ruby
test:postgres:
<< *job_definition
services: *postgres_definition
test:mysql:
<< *job_definition
services: *mysql_definition
```
The expanded version looks like this:
```yaml
.job_template:
script:
- test project
.postgres_services:
services:
- postgres
- ruby
.mysql_services:
services:
- mysql
- ruby
test:postgres:
script:
- test project
services:
- postgres
- ruby
test:mysql:
script:
- test project
services:
- mysql
- ruby
```
You can see that the hidden jobs are conveniently used as templates.
## Validate the .gitlab-ci.yml
Each instance of GitLab CI has an embedded debug tool called Lint.
......
......@@ -13,7 +13,6 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
thumbsup = page.first('.award-control')
thumbsup.click
thumbsup.hover
sleep 0.3
end
end
......@@ -46,12 +45,10 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end
step 'I have award added' do
sleep 0.2
page.within '.awards' do
expect(page).to have_selector '.js-emoji-btn'
expect(page.find('.js-emoji-btn.active .js-counter')).to have_content '1'
expect(page.find('.js-emoji-btn.active')['data-original-title']).to eq('me')
expect(page).to have_css(".js-emoji-btn.active[data-original-title='me']")
end
end
......
......@@ -68,7 +68,7 @@ module SharedBuilds
end
step 'I see the build' do
page.within('.commit_status') do
page.within('.build') do
expect(page).to have_content "##{@build.id}"
expect(page).to have_content @build.sha[0..7]
expect(page).to have_content @build.ref
......
......@@ -434,13 +434,6 @@ module API
expose :id, :status, :stage, :name, :ref, :tag, :coverage
expose :created_at, :started_at, :finished_at
expose :user, with: User
# TODO: download_url in Ci:Build model is an GitLab Web Interface URL, not API URL. We should think on some API
# for downloading of artifacts (see: https://gitlab.com/gitlab-org/gitlab-ce/issues/4255)
expose :download_url do |repo_obj, options|
if options[:user_can_download_artifacts]
repo_obj.artifacts_download_url
end
end
expose :artifacts_file, using: BuildArtifactFile, if: -> (build, opts) { build.artifacts? }
expose :commit, with: RepoCommit do |repo_obj, _options|
if repo_obj.respond_to?(:commit)
......
......@@ -5,7 +5,9 @@ module Ci
DEFAULT_STAGES = %w(build test deploy)
DEFAULT_STAGE = 'test'
ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables, :cache]
ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts, :cache]
ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services,
:allow_failure, :type, :stage, :when, :artifacts, :cache,
:dependencies]
attr_reader :before_script, :image, :services, :variables, :path, :cache
......@@ -60,6 +62,7 @@ module Ci
@jobs = {}
@config.each do |key, job|
next if key.to_s.start_with?('.')
stage = job[:stage] || job[:type] || DEFAULT_STAGE
@jobs[key] = { stage: stage }.merge(job)
end
......@@ -81,6 +84,7 @@ module Ci
services: job[:services] || @services,
artifacts: job[:artifacts],
cache: job[:cache] || @cache,
dependencies: job[:dependencies],
}.compact
}
end
......@@ -143,6 +147,7 @@ module Ci
validate_job_stage!(name, job) if job[:stage]
validate_job_cache!(name, job) if job[:cache]
validate_job_artifacts!(name, job) if job[:artifacts]
validate_job_dependencies!(name, job) if job[:dependencies]
end
private
......@@ -216,6 +221,10 @@ module Ci
end
def validate_job_artifacts!(name, job)
if job[:artifacts][:name] && !validate_string(job[:artifacts][:name])
raise ValidationError, "#{name} job: artifacts:name parameter should be a string"
end
if job[:artifacts][:untracked] && !validate_boolean(job[:artifacts][:untracked])
raise ValidationError, "#{name} job: artifacts:untracked parameter should be an boolean"
end
......@@ -225,6 +234,22 @@ module Ci
end
end
def validate_job_dependencies!(name, job)
if !validate_array_of_strings(job[:dependencies])
raise ValidationError, "#{name} job: dependencies parameter should be an array of strings"
end
stage_index = stages.index(job[:stage])
job[:dependencies].each do |dependency|
raise ValidationError, "#{name} job: undefined dependency: #{dependency}" unless @jobs[dependency]
unless stages.index(@jobs[dependency][:stage]) < stage_index
raise ValidationError, "#{name} job: dependency #{dependency} is not defined in prior stages"
end
end
end
def validate_array_of_strings(values)
values.is_a?(Array) && values.all? { |value| validate_string(value) }
end
......
......@@ -397,7 +397,7 @@ module Ci
services: ["mysql"],
before_script: ["pwd"],
rspec: {
artifacts: { paths: ["logs/", "binaries/"], untracked: true },
artifacts: { paths: ["logs/", "binaries/"], untracked: true, name: "custom_name" },
script: "rspec"
}
})
......@@ -417,6 +417,7 @@ module Ci
image: "ruby:2.1",
services: ["mysql"],
artifacts: {
name: "custom_name",
paths: ["logs/", "binaries/"],
untracked: true
}
......@@ -427,6 +428,73 @@ module Ci
end
end
describe "Dependencies" do
let(:config) do
{
build1: { stage: 'build', script: 'test' },
build2: { stage: 'build', script: 'test' },
test1: { stage: 'test', script: 'test', dependencies: dependencies },
test2: { stage: 'test', script: 'test' },
deploy: { stage: 'test', script: 'test' }
}
end
subject { GitlabCiYamlProcessor.new(YAML.dump(config)) }
context 'no dependencies' do
let(:dependencies) { }
it { expect { subject }.to_not raise_error }
end
context 'dependencies to builds' do
let(:dependencies) { [:build1, :build2] }
it { expect { subject }.to_not raise_error }
end
context 'undefined dependency' do
let(:dependencies) { [:undefined] }
it { expect { subject }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'test1 job: undefined dependency: undefined') }
end
context 'dependencies to deploy' do
let(:dependencies) { [:deploy] }
it { expect { subject }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'test1 job: dependency deploy is not defined in prior stages') }
end
end
describe "Hidden jobs" do
let(:config) do
YAML.dump({
'.hidden_job' => { script: 'test' },
'normal_job' => { script: 'test' }
})
end
let(:config_processor) { GitlabCiYamlProcessor.new(config) }
subject { config_processor.builds_for_stage_and_ref("test", "master") }
it "doesn't create jobs that starts with dot" do
expect(subject.size).to eq(1)
expect(subject.first).to eq({
except: nil,
stage: "test",
stage_idx: 1,
name: :normal_job,
only: nil,
commands: "\ntest",
tag_list: [],
options: {},
when: "on_success",
allow_failure: false
})
end
end
describe "YAML Alias/Anchor" do
it "is correctly supported for jobs" do
config = <<EOT
......@@ -629,6 +697,13 @@ EOT
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always")
end
it "returns errors if job artifacts:name is not an a string" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { name: 1 } } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:name parameter should be a string")
end
it "returns errors if job artifacts:untracked is not an array of strings" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } })
expect do
......@@ -684,6 +759,13 @@ EOT
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:paths parameter should be an array of strings")
end
it "returns errors if job dependencies is not an array of strings" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", dependencies: "string" } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: dependencies parameter should be an array of strings")
end
end
end
end
......@@ -9,7 +9,7 @@ describe Ci::Build, models: true do
it { is_expected.to respond_to :trace_html }
describe :first_pending do
describe '#first_pending' do
let(:first) { FactoryGirl.create :ci_build, commit: commit, status: 'pending', created_at: Date.yesterday }
let(:second) { FactoryGirl.create :ci_build, commit: commit, status: 'pending' }
before { first; second }
......@@ -19,7 +19,7 @@ describe Ci::Build, models: true do
it('returns with the first pending build') { is_expected.to eq(first) }
end
describe :create_from do
describe '#create_from' do
before do
build.status = 'success'
build.save
......@@ -33,7 +33,7 @@ describe Ci::Build, models: true do
end
end
describe :ignored? do
describe '#ignored?' do
subject { build.ignored? }
context 'if build is not allowed to fail' do
......@@ -69,7 +69,7 @@ describe Ci::Build, models: true do
end
end
describe :trace do
describe '#trace' do
subject { build.trace_html }
it { is_expected.to be_empty }
......@@ -101,7 +101,7 @@ describe Ci::Build, models: true do
# it { is_expected.to eq(commit.project.timeout) }
# end
describe :options do
describe '#options' do
let(:options) do
{
image: "ruby:2.1",
......@@ -122,25 +122,25 @@ describe Ci::Build, models: true do
# it { is_expected.to eq(project.allow_git_fetch) }
# end
describe :project do
describe '#project' do
subject { build.project }
it { is_expected.to eq(commit.project) }
end
describe :project_id do
describe '#project_id' do
subject { build.project_id }
it { is_expected.to eq(commit.project_id) }
end
describe :project_name do
describe '#project_name' do
subject { build.project_name }
it { is_expected.to eq(project.name) }
end
describe :extract_coverage do
describe '#extract_coverage' do
context 'valid content & regex' do
subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') }
......@@ -172,7 +172,7 @@ describe Ci::Build, models: true do
end
end
describe :variables do
describe '#variables' do
context 'returns variables' do
subject { build.variables }
......@@ -242,7 +242,7 @@ describe Ci::Build, models: true do
end
end
describe :can_be_served? do
describe '#can_be_served?' do
let(:runner) { FactoryGirl.create :ci_runner }
before { build.project.runners << runner }
......@@ -277,7 +277,7 @@ describe Ci::Build, models: true do
end
end
describe :any_runners_online? do
describe '#any_runners_online?' do
subject { build.any_runners_online? }
context 'when no runners' do
......@@ -312,8 +312,8 @@ describe Ci::Build, models: true do
end
end
describe :show_warning? do
subject { build.show_warning? }
describe '#stuck?' do
subject { build.stuck? }
%w(pending).each do |state|
context "if commit_status.status is #{state}" do
......@@ -343,35 +343,7 @@ describe Ci::Build, models: true do
end
end
describe :artifacts_download_url do
subject { build.artifacts_download_url }
context 'artifacts file does not exist' do
before { build.update_attributes(artifacts_file: nil) }
it { is_expected.to be_nil }
end
context 'artifacts file exists' do
let(:build) { create(:ci_build, :artifacts) }
it { is_expected.to_not be_nil }
end
end
describe :artifacts_browse_url do
subject { build.artifacts_browse_url }
it "should be nil if artifacts browser is unsupported" do
allow(build).to receive(:artifacts_metadata?).and_return(false)
is_expected.to be_nil
end
it 'should not be nil if artifacts browser is supported' do
allow(build).to receive(:artifacts_metadata?).and_return(true)
is_expected.to_not be_nil
end
end
describe :artifacts? do
describe '#artifacts?' do
subject { build.artifacts? }
context 'artifacts archive does not exist' do
......@@ -386,7 +358,7 @@ describe Ci::Build, models: true do
end
describe :artifacts_metadata? do
describe '#artifacts_metadata?' do
subject { build.artifacts_metadata? }
context 'artifacts metadata does not exist' do
it { is_expected.to be_falsy }
......@@ -398,7 +370,7 @@ describe Ci::Build, models: true do
end
end
describe :repo_url do
describe '#repo_url' do
let(:build) { FactoryGirl.create :ci_build }
let(:project) { build.project }
......@@ -412,7 +384,7 @@ describe Ci::Build, models: true do
it { is_expected.to include(project.web_url[7..-1]) }
end
describe :depends_on_builds do
describe '#depends_on_builds' do
let!(:build) { FactoryGirl.create :ci_build, commit: commit, name: 'build', stage_idx: 0, stage: 'build' }
let!(:rspec_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rspec', stage_idx: 1, stage: 'test' }
let!(:rubocop_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rubocop', stage_idx: 1, stage: 'test' }
......@@ -444,7 +416,7 @@ describe Ci::Build, models: true do
created_at: created_at)
end
describe :merge_request do
describe '#merge_request' do
context 'when a MR has a reference to the commit' do
before do
@merge_request = create_mr(build, commit, factory: :merge_request)
......
......@@ -32,50 +32,6 @@ describe Ci::Commit, models: true do
it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha }
describe :ordered do
let(:project) { FactoryGirl.create :empty_project }
it 'returns ordered list of commits' do
commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hours.ago, project: project
expect(project.ci_commits.ordered).to eq([commit2, commit1])
end
it 'returns commits ordered by committed_at and id, with nulls last' do
commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
commit2 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hours.ago, project: project
commit4 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
expect(project.ci_commits.ordered).to eq([commit2, commit4, commit3, commit1])
end
end
describe :last_build do
subject { commit.last_build }
before do
@first = FactoryGirl.create :ci_build, commit: commit, created_at: Date.yesterday
@second = FactoryGirl.create :ci_build, commit: commit
end
it { is_expected.to be_a(Ci::Build) }
it('returns with the most recently created build') { is_expected.to eq(@second) }
end
describe :retry do
before do
@first = FactoryGirl.create :ci_build, commit: commit, created_at: Date.yesterday
@second = FactoryGirl.create :ci_build, commit: commit
end
it "creates only a new build" do
expect(commit.builds.count(:all)).to eq 2
expect(commit.statuses.count(:all)).to eq 2
commit.retry
expect(commit.builds.count(:all)).to eq 3
expect(commit.statuses.count(:all)).to eq 3
end
end
describe :valid_commit_sha do
context 'commit.sha can not start with 00000000' do
before do
......
......@@ -152,7 +152,7 @@ describe Note, models: true do
end
end
describe :grouped_awards do
describe '.grouped_awards' do
before do
create :note, note: "smile", is_award: true
create :note, note: "smile", is_award: true
......@@ -169,6 +169,66 @@ describe Note, models: true do
end
end
describe '#active?' do
it 'is always true when the note has no associated diff' do
note = build(:note)
expect(note).to receive(:diff).and_return(nil)
expect(note).to be_active
end
it 'is never true when the note has no noteable associated' do
note = build(:note)
expect(note).to receive(:diff).and_return(double)
expect(note).to receive(:noteable).and_return(nil)
expect(note).not_to be_active
end
it 'returns the memoized value if defined' do
note = build(:note)
expect(note).to receive(:diff).and_return(double)
expect(note).to receive(:noteable).and_return(double)
note.instance_variable_set(:@active, 'foo')
expect(note).not_to receive(:find_noteable_diff)
expect(note.active?).to eq 'foo'
end
context 'for a merge request noteable' do
it 'is false when noteable has no matching diff' do
merge = build_stubbed(:merge_request, :simple)
note = build(:note, noteable: merge)
allow(note).to receive(:diff).and_return(double)
expect(note).to receive(:find_noteable_diff).and_return(nil)
expect(note).not_to be_active
end
it 'is true when noteable has a matching diff' do
merge = create(:merge_request, :simple)
# Generate a real line_code value so we know it will match. We use a
# random line from a random diff just for funsies.
diff = merge.diffs.to_a.sample
line = Gitlab::Diff::Parser.new.parse(diff.diff.each_line).to_a.sample
code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
# We're persisting in order to trigger the set_diff callback
note = create(:note, noteable: merge, line_code: code)
# Make sure we don't get a false positive from a guard clause
expect(note).to receive(:find_noteable_diff).and_call_original
expect(note).to be_active
end
end
end
describe "editable?" do
it "returns true" do
note = build(:note)
......
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