Commit 03a6d379 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into ce_upstream

parents 31a87ef8 6593e3ce
Please view this file on the master branch, on stable branches it's out of date.
v 8.11.0 (unreleased)
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
v 8.10.0 (unreleased)
- Fix profile activity heatmap to show correct day name (eanplatter)
......@@ -35,12 +37,15 @@ v 8.10.0 (unreleased)
- Support U2F devices in Firefox. !5177
- Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
- Add Spring EmojiOne updates.
- Added Rake task for tracking deployments !5320
- Fix fetching LFS objects for private CI projects
- Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237
- Add syntax for multiline blockquote using `>>>` fence !3954
- Fix viewing notification settings when a project is pending deletion
- Updated compare dropdown menus to use GL dropdown
- Redirects back to issue after clicking login link
- Eager load award emoji on notes
- Allow to define manual actions/builds on Pipelines and Environments
- Fix pagination when sorting by columns with lots of ties (like priority)
- The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020
- Updated project header design
......@@ -56,6 +61,7 @@ v 8.10.0 (unreleased)
- API: Expose `due_date` for issues (Robert Schilling)
- API: Todos !3188 (Robert Schilling)
- API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling)
- API: Expose `developers_can_push` and `developers_can_merge` for branches !5208 (Robert Schilling)
- Add "Enabled Git access protocols" to Application Settings
- Diffs will create button/diff form on demand no on server side
- Reduce size of HTML used by diff comment forms
......@@ -124,6 +130,7 @@ v 8.10.0 (unreleased)
- Allow empty repositories on project import/export
- Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska)
- Allow bulk (un)subscription from issues in issue index
- Fix MR diff encoding issues exporting GitLab projects
v 8.9.6
- Fix importing of events under notes for GitLab projects. !5154
......
# GitLab Maintenance Policy
GitLab is a fast moving and evolving project. We currently don't have the resources to support many releases concurrently. We support exactly one stable release at any given time.
GitLab follows the [Semantic Versioning](http://semver.org/) for its releases:
`(Major).(Minor).(Patch)` in a [pragmatic way].
GitLab follows the [Semantic Versioning](http://semver.org/) for its releases: `(Major).(Minor).(Patch)` in a [pragmatic way](https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e).
- **Major version**: Whenever there is something significant or any backwards
incompatible changes are introduced to the public API.
- **Minor version**: When new, backwards compatible functionality is introduced
to the public API or a minor feature is introduced, or when a set of smaller
features is rolled out.
- **Patch number**: When backwards compatible bug fixes are introduced that fix
incorrect behavior.
- **Major version**: Whenever there is something significant or any backwards incompatible changes are introduced to the public API.
- **Minor version**: When new, backwards compatible functionality is introduced to the public API or a minor feature is introduced, or when a set of smaller features is rolled out.
- **Patch number**: When backwards compatible bug fixes are introduced that fix incorrect behavior.
The current stable release will receive security patches and bug fixes
(eg. `8.9.0` -> `8.9.1`). Feature releases will mark the next supported stable
release where the minor version is increased numerically by increments of one
(eg. `8.9 -> 8.10`).
The current stable release will receive security patches and bug fixes (eg. `5.0` -> `5.0.1`). Feature releases will mark the next supported stable release where the minor version is increased numerically by increments of one (eg. `5.0 -> 5.1`).
Our current policy is to support one stable release at any given time, but for
medium-level security issues, we may consider [backporting to the previous two
monthly releases][rel-sec].
We encourage everyone to run the latest stable release to ensure that you can easily upgrade to the most secure and feature rich GitLab experience. In order to make sure you can easily run the most recent stable release, we are working hard to keep the update process simple and reliable.
We encourage everyone to run the latest stable release to ensure that you can
easily upgrade to the most secure and feature-rich GitLab experience. In order
to make sure you can easily run the most recent stable release, we are working
hard to keep the update process simple and reliable.
More information about the release procedures can be found in the doc/release directory.
More information about the release procedures can be found in our
[release-tools documentation][rel]. You may also want to read our
[Responsible Disclosure Policy][disclosure].
[rel-sec]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/security.md#backporting
[rel]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/
[disclosure]: https://about.gitlab.com/disclosure/
[pragmatic way]: https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e
......@@ -16,7 +16,7 @@ $border-color: #e5e5e5;
$focus-border-color: #3aabf0;
$table-border-color: #f0f0f0;
$background-color: #fafafa;
$dark-background-color: #f7f7f7;
$dark-background-color: #f5f5f5;
$table-text-gray: #8f8f8f;
/*
......
......@@ -63,7 +63,7 @@ form.edit-issue {
.merge-request,
.issue {
&.today {
background: #f8feef;
background: #f3fff2;
border-color: #e1e8d5;
}
......
.pipelines {
.stage {
max-width: 80px;
width: 80px;
max-width: 90px;
width: 90px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
......@@ -30,13 +30,17 @@
}
.table.builds {
min-width: 1100px;
min-width: 1200px;
tr {
th {
padding: 16px;
padding: 16px 8px;
border: none;
}
td {
padding: 10px 8px;
}
}
tbody {
......@@ -53,9 +57,8 @@
.branch-commit {
.branch-name {
margin-left: 8px;
font-weight: bold;
max-width: 180px;
max-width: 150px;
overflow: hidden;
display: inline-block;
white-space: nowrap;
......@@ -64,10 +67,15 @@
}
svg {
margin: 0 6px;
height: 14px;
width: auto;
vertical-align: middle;
fill: $table-text-gray;
}
.fa {
font-size: 12px;
color: $table-text-gray;
}
.commit-id {
......@@ -100,6 +108,22 @@
}
}
.icon-container {
display: inline-block;
text-align: right;
width: 20px;
.fa {
position: relative;
right: 3px;
}
svg {
position: relative;
right: 1px;
}
}
.duration,
.finished-at {
color: $table-text-gray;
......@@ -107,21 +131,19 @@
.fa {
font-size: 12px;
margin-right: 4px;
}
svg {
height: 12px;
width: auto;
width: 12px;
height: auto;
vertical-align: middle;
}
.fa,
svg {
margin-right: 5px;
margin-right: 4px;
}
}
.pipeline-actions {
min-width: 140px;
.btn {
margin: 0;
......
......@@ -19,7 +19,7 @@
border-top: 1px solid $table-border-gray;
td, th {
line-height: 23px;
line-height: 21px;
}
&:hover {
......
class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry, :play]
before_action :authorize_update_build!, except: [:index, :show, :status, :raw]
layout 'project'
......@@ -49,14 +49,19 @@ class Projects::BuildsController < Projects::ApplicationController
end
def retry
unless @build.retryable?
return render_404
end
return render_404 unless @build.retryable?
build = Ci::Build.retry(@build, current_user)
redirect_to build_path(build)
end
def play
return render_404 unless @build.playable?
build = @build.play(current_user)
redirect_to build_path(build)
end
def cancel
@build.cancel
redirect_to build_path(@build)
......
......@@ -15,6 +15,7 @@ module Ci
scope :with_artifacts, ->() { where.not(artifacts_file: nil) }
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
scope :manual_actions, ->() { where(when: :manual) }
mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader
......@@ -91,6 +92,29 @@ module Ci
end
end
def manual?
self.when == 'manual'
end
def other_actions
pipeline.manual_actions.where.not(id: self)
end
def playable?
project.builds_enabled? && commands.present? && manual?
end
def play(current_user = nil)
# Try to queue a current build
if self.queue
self.update(user: current_user)
self
else
# Otherwise we need to create a duplicate
Ci::Build.retry(self, current_user)
end
end
def retryable?
project.builds_enabled? && commands.present? && complete?
end
......@@ -121,12 +145,7 @@ module Ci
end
def variables
variables = []
variables += predefined_variables
variables += yaml_variables if yaml_variables
variables += project_variables
variables += trigger_variables
variables
predefined_variables + yaml_variables + project_variables + trigger_variables
end
def merge_request
......@@ -386,6 +405,14 @@ module Ci
self.update(artifacts_expire_at: nil)
end
def when
read_attribute(:when) || build_attributes_from_config[:when] || 'on_success'
end
def yaml_variables
read_attribute(:yaml_variables) || build_attributes_from_config[:yaml_variables] || []
end
private
def update_artifacts_size
......@@ -428,5 +455,11 @@ module Ci
variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request
variables
end
def build_attributes_from_config
return {} unless pipeline.config_processor
pipeline.config_processor.build_attributes(name)
end
end
end
......@@ -69,6 +69,10 @@ module Ci
!tag?
end
def manual_actions
builds.latest.manual_actions
end
def retryable?
builds.latest.any? do |build|
build.failed? && build.retryable?
......
......@@ -22,6 +22,10 @@ class CommitStatus < ActiveRecord::Base
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
state_machine :status, initial: :pending do
event :queue do
transition skipped: :pending
end
event :run do
transition pending: :running
end
......
......@@ -16,10 +16,10 @@ module Statuseable
deduce_status = "(CASE
WHEN (#{builds})=0 THEN NULL
WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success'
WHEN (#{builds})=(#{pending}) THEN 'pending'
WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored}) THEN 'canceled'
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending'
WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
WHEN (#{running})+(#{pending})>0 THEN 'running'
ELSE 'failed'
END)"
......
......@@ -32,4 +32,8 @@ class Deployment < ActiveRecord::Base
def keep_around_commit
project.repository.keep_around(self.sha)
end
def manual_actions
deployable.try(:other_actions)
end
end
class MergeRequestDiff < ActiveRecord::Base
include Sortable
include Importable
include EncodingHelper
# Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 100
......@@ -211,6 +212,14 @@ class MergeRequestDiff < ActiveRecord::Base
branch_base_commit.try(:sha)
end
def utf8_st_diffs
st_diffs.map do |diff|
diff.each do |k, v|
diff[k] = encode_utf8(v) if v.respond_to?(:encoding)
end
end
end
#
# #save or #update_attributes providing changes on serialized attributes do a lot of
# serialization and deserialization calls resulting in bad performance.
......
......@@ -783,6 +783,7 @@ class Repository
options[:commit] = {
message: message,
branch: ref,
update_ref: false,
}
raw_repository.mkdir(path, options)
......@@ -798,6 +799,7 @@ class Repository
options[:commit] = {
message: message,
branch: ref,
update_ref: false,
}
options[:file] = {
......@@ -818,7 +820,8 @@ class Repository
options[:author] = committer
options[:commit] = {
message: message,
branch: ref
branch: ref,
update_ref: false,
}
options[:file] = {
......@@ -870,11 +873,10 @@ class Repository
merge_index = rugged.merge_commits(our_commit, their_commit)
return false if merge_index.conflicts?
commit_with_hooks(user, merge_request.target_branch) do |tmp_ref|
commit_with_hooks(user, merge_request.target_branch) do
actual_options = options.merge(
parents: [our_commit, their_commit],
tree: merge_index.write_tree(rugged),
update_ref: tmp_ref
)
commit_id = Rugged::Commit.create(rugged, actual_options)
......@@ -889,15 +891,14 @@ class Repository
return false unless revert_tree_id
commit_with_hooks(user, base_branch) do |ref|
commit_with_hooks(user, base_branch) do
committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged,
message: commit.revert_message,
author: committer,
committer: committer,
tree: revert_tree_id,
parents: [rugged.lookup(source_sha)],
update_ref: ref)
parents: [rugged.lookup(source_sha)])
end
end
......@@ -907,7 +908,7 @@ class Repository
return false unless cherry_pick_tree_id
commit_with_hooks(user, base_branch) do |ref|
commit_with_hooks(user, base_branch) do
committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged,
message: commit.message,
......@@ -918,8 +919,7 @@ class Repository
},
committer: committer,
tree: cherry_pick_tree_id,
parents: [rugged.lookup(source_sha)],
update_ref: ref)
parents: [rugged.lookup(source_sha)])
end
end
......@@ -1119,20 +1119,6 @@ class Repository
Gitlab::Popen.popen(args, path_to_repo)
end
def with_tmp_ref(oldrev = nil)
random_string = SecureRandom.hex
tmp_ref = "refs/tmp/#{random_string}/head"
if oldrev && !Gitlab::Git.blank_ref?(oldrev)
rugged.references.create(tmp_ref, oldrev)
end
# Make commit in tmp ref
yield(tmp_ref)
ensure
rugged.references.delete(tmp_ref) rescue nil
end
def commit_with_hooks(current_user, branch)
update_autocrlf_option
......@@ -1145,33 +1131,31 @@ class Repository
oldrev = target_branch.target
end
with_tmp_ref(oldrev) do |tmp_ref|
# Make commit in tmp ref
newrev = yield(tmp_ref)
# Make commit
newrev = yield(ref)
unless newrev
raise CommitError.new('Failed to create commit')
end
unless newrev
raise CommitError.new('Failed to create commit')
end
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
if was_empty || !target_branch
# Create branch
rugged.references.create(ref, newrev)
else
# Update head
current_head = find_branch(branch).target
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
if was_empty || !target_branch
# Create branch
rugged.references.create(ref, newrev)
# Make sure target branch was not changed during pre-receive hook
if current_head == oldrev
rugged.references.update(ref, newrev)
else
# Update head
current_head = find_branch(branch).target
# Make sure target branch was not changed during pre-receive hook
if current_head == oldrev
rugged.references.update(ref, newrev)
else
raise CommitError.new('Commit was rejected because branch received new push')
end
raise CommitError.new('Commit was rejected because branch received new push')
end
end
newrev
end
newrev
end
def ls_files(ref)
......
......@@ -15,7 +15,7 @@ module Ci
status == 'success'
when 'on_failure'
status == 'failed'
when 'always'
when 'always', 'manual'
%w(success failed).include?(status)
end
end
......@@ -47,6 +47,10 @@ module Ci
user: user,
project: @pipeline.project)
# TODO: The proper implementation for this is in
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5295
build_attrs[:status] = 'skipped' if build_attrs[:when] == 'manual'
##
# We do not persist new builds here.
# Those will be persisted when @pipeline is saved.
......
......@@ -15,21 +15,19 @@ class CreateBranchService < BaseService
return error('Branch already exists')
end
new_branch = nil
if source_project != @project
repository.with_tmp_ref do |tmp_ref|
repository.fetch_ref(
source_project.repository.path_to_repo,
"refs/heads/#{ref}",
tmp_ref
)
new_branch = repository.add_branch(current_user, branch_name, tmp_ref)
end
else
new_branch = repository.add_branch(current_user, branch_name, ref)
end
new_branch = if source_project != @project
repository.fetch_ref(
source_project.repository.path_to_repo,
"refs/heads/#{ref}",
"refs/heads/#{branch_name}"
)
repository.after_create_branch
repository.find_branch(branch_name)
else
repository.add_branch(current_user, branch_name, ref)
end
if new_branch
success(new_branch)
......
......@@ -39,6 +39,8 @@
%span.label.label-danger allowed to fail
- if defined?(retried) && retried
%span.label.label-warning retried
- if build.manual?
%span.label.label-info manual
- if defined?(runner) && runner
......@@ -79,6 +81,11 @@
- 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', class: 'btn btn-build' do
= icon('remove', class: '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', class: 'btn btn-build' do
= icon('repeat')
- elsif defined?(allow_retry) && allow_retry
- if build.retryable?
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
= icon('repeat')
- elsif build.playable?
= link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
= icon('play')
......@@ -10,12 +10,13 @@
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
%span ##{pipeline.id}
- if pipeline.ref
.icon-container
= pipeline.tag? ? icon('tag') : icon('code-fork')
= link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace branch-name"
= custom_icon("icon_commit")
.icon-container
= custom_icon("icon_commit")
= link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace"
- if pipeline.tag?
%span.label.label-primary tag
- elsif pipeline.latest?
- if pipeline.latest?
%span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
- if pipeline.triggered?
%span.label.label-primary triggered
......@@ -57,18 +58,31 @@
%td.pipeline-actions
.controls.hidden-xs.pull-right
- artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
- if artifacts.present?
.inline
.btn-group
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
= icon("download")
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- artifacts.each do |build|
%li
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
= icon("download")
%span Download '#{build.name}' artifacts
- actions = pipeline.manual_actions
- if artifacts.present? || actions.any?
.btn-group.inline
- if actions.any?
.btn-group
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
= icon("play")
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |build|
%li
= link_to play_namespace_project_build_path(@project.namespace, @project, build), method: :post, rel: 'nofollow' do
= icon("play")
%span= build.name.humanize
- if artifacts.present?
.btn-group
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
= icon("download")
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- artifacts.each do |build|
%li
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
= icon("download")
%span Download '#{build.name}' artifacts
- if can?(current_user, :update_pipeline, @project)
.cancel-retry-btns.inline
......
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
.pull-right
- actions = deployment.manual_actions
- if actions.present?
.btn-group.inline
.btn-group
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
= icon("play")
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |action|
%li
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
= icon("play")
%span= action.name.humanize
- if local_assigns.fetch(:allow_rollback, false)
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do
- if deployment.last?
Retry
- else
Rollback
......@@ -7,17 +7,11 @@
%td
- if deployment.deployable
= link_to namespace_project_build_path(@project.namespace, @project, deployment.deployable) do
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable] do
= "#{deployment.deployable.name} (##{deployment.deployable.id})"
%td
#{time_ago_with_tooltip(deployment.created_at)}
%td
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
.pull-right
= link_to retry_namespace_project_build_path(@project.namespace, @project, deployment.deployable), method: :post, class: 'btn btn-build' do
- if deployment.last?
Retry
- else
Rollback
= render 'projects/deployments/actions', deployment: deployment, allow_rollback: true
......@@ -15,3 +15,6 @@
%td
- if last_deployment
#{time_ago_with_tooltip(last_deployment.created_at)}
%td
= render 'projects/deployments/actions', deployment: last_deployment
......@@ -28,4 +28,5 @@
%th Environment
%th Last deployment
%th Date
%th
= render @environments
......@@ -5,7 +5,7 @@
%div{ class: container_class }
.top-area
.col-md-9
%h3.page-title= @environment.name.titleize
%h3.page-title= @environment.name.capitalize
.col-md-3
.nav-controls
......
......@@ -14,9 +14,9 @@
.disabled-comment.text-center
.disabled-comment-text.inline
Please
= link_to "register",new_user_session_path
= link_to "register", new_session_path(:user, redirect_to_referer: 'yes')
or
= link_to "login",new_user_session_path
= link_to "login", new_session_path(:user, redirect_to_referer: 'yes')
to post a comment
:javascript
......
# Description: https://coderwall.com/p/heed_q/rails-routing-and-namespaced-models
#
# This allows us to use CI ActiveRecord objects in all routes and use it:
# - [project.namespace, project, build]
#
# instead of:
# - namespace_project_build_path(project.namespace, project, build)
#
# Without that, Ci:: namespace is used for resolving routes:
# - namespace_project_ci_build_path(project.namespace, project, build)
module Ci
def self.use_relative_model_naming?
true
end
end
......@@ -811,6 +811,7 @@ Rails.application.routes.draw do
get :status
post :cancel
post :retry
post :play
post :erase
get :trace
get :raw
......
class Gitlab::Seeder::Builds
STAGES = %w[build notify_build test notify_test deploy notify_deploy]
def initialize(project)
@project = project
end
def seed!
ci_commits.each do |ci_commit|
pipelines.each do |pipeline|
begin
build_create!(ci_commit, name: 'test build 1')
build_create!(ci_commit, status: 'success', name: 'test build 2')
build_create!(pipeline, name: 'build:linux', stage: 'build')
build_create!(pipeline, name: 'build:osx', stage: 'build')
build_create!(pipeline, name: 'slack post build', stage: 'notify_build')
build_create!(pipeline, name: 'rspec:linux', stage: 'test')
build_create!(pipeline, name: 'rspec:windows', stage: 'test')
build_create!(pipeline, name: 'rspec:windows', stage: 'test')
build_create!(pipeline, name: 'rspec:osx', stage: 'test')
build_create!(pipeline, name: 'spinach:linux', stage: 'test')
build_create!(pipeline, name: 'spinach:osx', stage: 'test')
build_create!(pipeline, name: 'cucumber:linux', stage: 'test')
build_create!(pipeline, name: 'cucumber:osx', stage: 'test')
build_create!(pipeline, name: 'slack post test', stage: 'notify_test')
build_create!(pipeline, name: 'staging', stage: 'deploy', environment: 'staging')
build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual')
commit_status_create!(pipeline, name: 'jenkins')
print '.'
rescue ActiveRecord::RecordInvalid
print 'F'
......@@ -15,8 +36,8 @@ class Gitlab::Seeder::Builds
end
end
def ci_commits
commits = @project.repository.commits('master', path: nil, limit: 5)
def pipelines
commits = @project.repository.commits('master', limit: 5)
commits_sha = commits.map { |commit| commit.raw.id }
commits_sha.map do |sha|
@project.ensure_pipeline(sha, 'master')
......@@ -25,11 +46,11 @@ class Gitlab::Seeder::Builds
[]
end
def build_create!(ci_commit, opts = {})
attributes = build_attributes_for(ci_commit).merge(opts)
def build_create!(pipeline, opts = {})
attributes = build_attributes_for(pipeline, opts)
build = Ci::Build.new(attributes)
if %w(success failed).include?(build.status)
if opts[:name].start_with?('build')
artifacts_cache_file(artifacts_archive_path) do |file|
build.artifacts_file = file
end
......@@ -40,19 +61,28 @@ class Gitlab::Seeder::Builds
end
build.save!
build.update(status: build_status)
if %w(running success failed).include?(build.status)
# We need to set build trace after saving a build (id required)
build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
end
end
def commit_status_create!(pipeline, opts = {})
attributes = commit_status_attributes_for(pipeline, opts)
GenericCommitStatus.create(attributes)
end
def commit_status_attributes_for(pipeline, opts)
{ name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]),
ref: 'master', user: build_user, project: @project, pipeline: pipeline,
created_at: Time.now, updated_at: Time.now
}.merge(opts)
end
def build_attributes_for(ci_commit)
{ name: 'test build', commands: "$ build command",
stage: 'test', stage_idx: 1, ref: 'master',
user_id: build_user, gl_project_id: @project.id,
status: build_status, commit_id: ci_commit.id,
created_at: Time.now, updated_at: Time.now }
def build_attributes_for(pipeline, opts)
commit_status_attributes_for(pipeline, opts).merge(commands: '$ build command')
end
def build_user
......@@ -63,13 +93,16 @@ class Gitlab::Seeder::Builds
Ci::Build::AVAILABLE_STATUSES.sample
end
def stage_index(stage)
STAGES.index(stage) || 0
end
def artifacts_archive_path
Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
end
def artifacts_metadata_path
Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz'
end
def artifacts_cache_file(file_path)
......
......@@ -23,6 +23,8 @@ Example response:
{
"name": "master",
"protected": true,
"developers_can_push": false,
"developers_can_merge": false,
"commit": {
"author_email": "john@example.com",
"author_name": "John Smith",
......@@ -64,6 +66,8 @@ Example response:
{
"name": "master",
"protected": true,
"developers_can_push": false,
"developers_can_merge": false,
"commit": {
"author_email": "john@example.com",
"author_name": "John Smith",
......@@ -91,13 +95,15 @@ PUT /projects/:id/repository/branches/:branch/protect
```
```bash
curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect
curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect?developers_can_push=true&developers_can_merge=true
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `branch` | string | yes | The name of the branch |
| `developers_can_push` | boolean | no | Flag if developers can push to the branch |
| `developers_can_merge` | boolean | no | Flag if developers can merge to the branch |
Example response:
......@@ -117,7 +123,9 @@ Example response:
]
},
"name": "master",
"protected": true
"protected": true,
"developers_can_push": true,
"developers_can_merge": true
}
```
......@@ -158,7 +166,9 @@ Example response:
]
},
"name": "master",
"protected": false
"protected": false,
"developers_can_push": false,
"developers_can_merge": false
}
```
......@@ -196,7 +206,9 @@ Example response:
]
},
"name": "newbranch",
"protected": false
"protected": false,
"developers_can_push": false,
"developers_can_merge": false
}
```
......
......@@ -277,8 +277,7 @@ Example Response:
## Mark all todos as done
Marks all pending todos for the current user as done. All todos marked as done
are returned in the response.
Marks all pending todos for the current user as done. It returns the number of marked todos.
```
DELETE /todos
......@@ -291,154 +290,7 @@ curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.c
Example Response:
```json
[
{
"id": 102,
"project": {
"id": 2,
"name": "Gitlab Ce",
"name_with_namespace": "Gitlab Org / Gitlab Ce",
"path": "gitlab-ce",
"path_with_namespace": "gitlab-org/gitlab-ce"
},
"author": {
"name": "Administrator",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/root"
},
"action_name": "marked",
"target_type": "MergeRequest",
"target": {
"id": 34,
"iid": 7,
"project_id": 2,
"title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
"description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.",
"state": "opened",
"created_at": "2016-06-17T07:49:24.419Z",
"updated_at": "2016-06-17T07:52:43.484Z",
"target_branch": "tutorials_git_tricks",
"source_branch": "DNSBL_docs",
"upvotes": 0,
"downvotes": 0,
"author": {
"name": "Maxie Medhurst",
"username": "craig_rutherford",
"id": 12,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/craig_rutherford"
},
"assignee": {
"name": "Administrator",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/root"
},
"source_project_id": 2,
"target_project_id": 2,
"labels": [],
"work_in_progress": false,
"milestone": {
"id": 32,
"iid": 2,
"project_id": 2,
"title": "v1.0",
"description": "Assumenda placeat ea voluptatem voluptate qui.",
"state": "active",
"created_at": "2016-06-17T07:47:34.163Z",
"updated_at": "2016-06-17T07:47:34.163Z",
"due_date": null
},
"merge_when_build_succeeds": false,
"merge_status": "cannot_be_merged",
"subscribed": true,
"user_notes_count": 7
},
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7",
"body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
"state": "done",
"created_at": "2016-06-17T07:52:35.225Z"
},
{
"id": 98,
"project": {
"id": 2,
"name": "Gitlab Ce",
"name_with_namespace": "Gitlab Org / Gitlab Ce",
"path": "gitlab-ce",
"path_with_namespace": "gitlab-org/gitlab-ce"
},
"author": {
"name": "Maxie Medhurst",
"username": "craig_rutherford",
"id": 12,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/craig_rutherford"
},
"action_name": "assigned",
"target_type": "MergeRequest",
"target": {
"id": 34,
"iid": 7,
"project_id": 2,
"title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
"description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.",
"state": "opened",
"created_at": "2016-06-17T07:49:24.419Z",
"updated_at": "2016-06-17T07:52:43.484Z",
"target_branch": "tutorials_git_tricks",
"source_branch": "DNSBL_docs",
"upvotes": 0,
"downvotes": 0,
"author": {
"name": "Maxie Medhurst",
"username": "craig_rutherford",
"id": 12,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/craig_rutherford"
},
"assignee": {
"name": "Administrator",
"username": "root",
"id": 1,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/root"
},
"source_project_id": 2,
"target_project_id": 2,
"labels": [],
"work_in_progress": false,
"milestone": {
"id": 32,
"iid": 2,
"project_id": 2,
"title": "v1.0",
"description": "Assumenda placeat ea voluptatem voluptate qui.",
"state": "active",
"created_at": "2016-06-17T07:47:34.163Z",
"updated_at": "2016-06-17T07:47:34.163Z",
"due_date": null
},
"merge_when_build_succeeds": false,
"merge_status": "cannot_be_merged",
"subscribed": true,
"user_notes_count": 7
},
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7",
"body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
"state": "done",
"created_at": "2016-06-17T07:49:24.624Z"
},
]
3
```
[ce-3188]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3188
......@@ -485,6 +485,7 @@ failure.
1. `on_failure` - execute build only when at least one build from prior stages
fails.
1. `always` - execute build regardless of the status of builds from prior stages.
1. `manual` - execute build manually.
For example:
......@@ -516,6 +517,7 @@ deploy_job:
stage: deploy
script:
- make deploy
when: manual
cleanup_job:
stage: cleanup
......@@ -527,7 +529,20 @@ cleanup_job:
The above script will:
1. Execute `cleanup_build_job` only when `build_job` fails
2. Always execute `cleanup_job` as the last step in pipeline.
2. Always execute `cleanup_job` as the last step in pipeline
3. Allow you to manually execute `deploy_job` from GitLab
#### Manual actions
>**Note:**
Introduced in GitLab 8.10.
Manual actions are special type of jobs that are not executed automatically in pipeline.
They need to be explicitly started by the user.
Manual actions can be started from pipelines, builds, environments and deployments views.
You can execute the same manual action multiple times.
Example usage of manual actions is deployment, ex. promote a staging environment to production.
### environment
......@@ -757,12 +772,13 @@ 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.
Note that `artifacts` from all 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.
Defining an empty array will skip downloading any artifacts for that job.
---
......
......@@ -167,3 +167,22 @@ of those assets. Unless you are modifying the JavaScript / CSS code on your
production machine after installing the package, there should be no reason to redo
rake assets:precompile on the production machine. If you suspect that assets
have been corrupted, you should reinstall the omnibus package.
## Tracking Deployments
GitLab provides a Rake task that lets you track deployments in GitLab
Performance Monitoring. This Rake task simply stores the current GitLab version
in the GitLab Performance Monitoring database.
For Omnibus-packages:
```
sudo gitlab-rake gitlab:track_deployment
```
For installations from source:
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production
```
......@@ -15,7 +15,8 @@ module API
# GET /projects/:id/repository/branches
get ":id/repository/branches" do
branches = user_project.repository.branches.sort_by(&:name)
present branches, with: Entities::RepoObject, project: user_project
present branches, with: Entities::RepoBranch, project: user_project
end
# Get a single branch
......@@ -28,7 +29,8 @@ module API
get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do
@branch = user_project.repository.branches.find { |item| item.name == params[:branch] }
not_found!("Branch") unless @branch
present @branch, with: Entities::RepoObject, project: user_project
present @branch, with: Entities::RepoBranch, project: user_project
end
# Protect a single branch
......@@ -36,6 +38,8 @@ module API
# Parameters:
# id (required) - The ID of a project
# branch (required) - The name of the branch
# developers_can_push (optional) - Flag if developers can push to that branch
# developers_can_merge (optional) - Flag if developers can merge to that branch
# Example Request:
# PUT /projects/:id/repository/branches/:branch/protect
put ':id/repository/branches/:branch/protect',
......@@ -43,11 +47,22 @@ module API
authorize_admin_project
@branch = user_project.repository.find_branch(params[:branch])
not_found!("Branch") unless @branch
not_found!('Branch') unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
user_project.protected_branches.create(name: @branch.name) unless protected_branch
developers_can_push = to_boolean(params[:developers_can_push])
developers_can_merge = to_boolean(params[:developers_can_merge])
if protected_branch
protected_branch.developers_can_push = developers_can_push unless developers_can_push.nil?
protected_branch.developers_can_merge = developers_can_merge unless developers_can_merge.nil?
protected_branch.save
else
user_project.protected_branches.create(name: @branch.name,
developers_can_push: developers_can_push || false,
developers_can_merge: developers_can_merge || false)
end
present @branch, with: Entities::RepoObject, project: user_project
present @branch, with: Entities::RepoBranch, project: user_project
end
# Unprotect a single branch
......@@ -66,7 +81,7 @@ module API
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
protected_branch.destroy if protected_branch
present @branch, with: Entities::RepoObject, project: user_project
present @branch, with: Entities::RepoBranch, project: user_project
end
# Create branch
......@@ -84,7 +99,7 @@ module API
if result[:status] == :success
present result[:branch],
with: Entities::RepoObject,
with: Entities::RepoBranch,
project: user_project
else
render_api_error!(result[:message], 400)
......
......@@ -129,21 +129,23 @@ module API
end
end
class RepoObject < Grape::Entity
class RepoBranch < Grape::Entity
expose :name
expose :commit do |repo_obj, options|
if repo_obj.respond_to?(:commit)
repo_obj.commit
elsif options[:project]
options[:project].repository.commit(repo_obj.target)
end
expose :commit do |repo_branch, options|
options[:project].repository.commit(repo_branch.target)
end
expose :protected do |repo, options|
if options[:project]
options[:project].protected_branch? repo.name
end
expose :protected do |repo_branch, options|
options[:project].protected_branch? repo_branch.name
end
expose :developers_can_push do |repo_branch, options|
options[:project].developers_can_push_to_protected_branch? repo_branch.name
end
expose :developers_can_merge do |repo_branch, options|
options[:project].developers_can_merge_to_protected_branch? repo_branch.name
end
end
......@@ -456,27 +458,14 @@ module API
end
class RepoTag < Grape::Entity
expose :name
expose :message do |repo_obj, _options|
if repo_obj.respond_to?(:message)
repo_obj.message
else
nil
end
end
expose :name, :message
expose :commit do |repo_obj, options|
if repo_obj.respond_to?(:commit)
repo_obj.commit
elsif options[:project]
options[:project].repository.commit(repo_obj.target)
end
expose :commit do |repo_tag, options|
options[:project].repository.commit(repo_tag.target)
end
expose :release, using: Entities::Release do |repo_obj, options|
if options[:project]
options[:project].releases.find_by(tag: repo_obj.name)
end
expose :release, using: Entities::Release do |repo_tag, options|
options[:project].releases.find_by(tag: repo_tag.name)
end
end
......
......@@ -9,6 +9,13 @@ module API
[ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(value)
end
def to_boolean(value)
return true if value =~ /^(true|t|yes|y|1|on)$/i
return false if value =~ /^(false|f|no|n|0|off)$/i
nil
end
def find_user_by_private_token
token_string = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
User.find_by_authentication_token(token_string) || User.find_by_personal_access_token(token_string)
......
......@@ -75,7 +75,7 @@ module API
todos = find_todos
todos.each(&:done)
present paginate(Kaminari.paginate_array(todos)), with: Entities::Todo, current_user: current_user
todos.length
end
end
end
......
......@@ -44,23 +44,51 @@ module Ci
end
def builds_for_ref(ref, tag = false, trigger_request = nil)
jobs_for_ref(ref, tag, trigger_request).map do |name, job|
build_job(name, job)
jobs_for_ref(ref, tag, trigger_request).map do |name, _|
build_attributes(name)
end
end
def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
jobs_for_stage_and_ref(stage, ref, tag, trigger_request).map do |name, job|
build_job(name, job)
jobs_for_stage_and_ref(stage, ref, tag, trigger_request).map do |name, _|
build_attributes(name)
end
end
def builds
@jobs.map do |name, job|
build_job(name, job)
@jobs.map do |name, _|
build_attributes(name)
end
end
def build_attributes(name)
job = @jobs[name.to_sym] || {}
{
stage_idx: @stages.index(job[:stage]),
stage: job[:stage],
##
# Refactoring note:
# - before script behaves differently than after script
# - after script returns an array of commands
# - before script should be a concatenated command
commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
tag_list: job[:tags] || [],
name: name,
allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success',
environment: job[:environment],
yaml_variables: yaml_variables(name),
options: {
image: job[:image] || @image,
services: job[:services] || @services,
artifacts: job[:artifacts],
cache: job[:cache] || @cache,
dependencies: job[:dependencies],
after_script: job[:after_script] || @after_script,
}.compact
}
end
private
def initial_parsing
......@@ -89,33 +117,6 @@ module Ci
@jobs[name] = { stage: stage }.merge(job)
end
def build_job(name, job)
{
stage_idx: @stages.index(job[:stage]),
stage: job[:stage],
##
# Refactoring note:
# - before script behaves differently than after script
# - after script returns an array of commands
# - before script should be a concatenated command
commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
tag_list: job[:tags] || [],
name: name,
allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success',
environment: job[:environment],
yaml_variables: yaml_variables(name),
options: {
image: job[:image] || @image,
services: job[:services] || @services,
artifacts: job[:artifacts],
cache: job[:cache] || @cache,
dependencies: job[:dependencies],
after_script: job[:after_script] || @after_script,
}.compact
}
end
def yaml_variables(name)
variables = global_variables.merge(job_variables(name))
variables.map do |key, value|
......@@ -194,8 +195,8 @@ module Ci
raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
end
if job[:when] && !job[:when].in?(%w[on_success on_failure always])
raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always"
if job[:when] && !job[:when].in?(%w[on_success on_failure always manual])
raise ValidationError, "#{name} job: when parameter should be on_success, on_failure, always or manual"
end
if job[:environment] && !validate_environment(job[:environment])
......
......@@ -2,7 +2,7 @@ module Gitlab
module ImportExport
extend self
VERSION = '0.1.1'
VERSION = '0.1.2'
FILENAME_LIMIT = 50
def export_path(relative_path:)
......
......@@ -53,7 +53,11 @@ included_attributes:
excluded_attributes:
snippets:
- :expired_at
merge_request_diff:
- :st_diffs
methods:
statuses:
- :type
\ No newline at end of file
- :type
merge_request_diff:
- :utf8_st_diffs
\ No newline at end of file
......@@ -33,6 +33,7 @@ module Gitlab
update_project_references
reset_ci_tokens if @relation_name == 'Ci::Trigger'
@relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data']
set_st_diffs if @relation_name == :merge_request_diff
generate_imported_object
end
......@@ -129,6 +130,10 @@ module Gitlab
def parsed_relation_hash
@relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
end
def set_st_diffs
@relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs')
end
end
end
end
namespace :gitlab do
desc 'GitLab | Tracks a deployment in GitLab Performance Monitoring'
task track_deployment: :environment do
metric = Gitlab::Metrics::Metric.
new('deployments', version: Gitlab::VERSION)
Gitlab::Metrics.submit_metrics([metric.to_hash])
end
end
......@@ -3,6 +3,8 @@ include ActionDispatch::TestProcess
FactoryGirl.define do
factory :ci_build, class: Ci::Build do
name 'test'
stage 'test'
stage_idx 0
ref 'master'
tag false
created_at 'Di 29. Okt 09:50:00 CET 2013'
......@@ -43,6 +45,11 @@ FactoryGirl.define do
status 'pending'
end
trait :manual do
status 'skipped'
self.when 'manual'
end
trait :allowed_to_fail do
allow_failure true
end
......
......@@ -13,6 +13,7 @@ feature 'Environments', feature: true do
describe 'when showing environments' do
given!(:environment) { }
given!(:deployment) { }
given!(:manual) { }
before do
visit namespace_project_environments_path(project.namespace, project)
......@@ -43,6 +44,24 @@ feature 'Environments', feature: true do
scenario 'does show deployment SHA' do
expect(page).to have_link(deployment.short_sha)
end
context 'with build and manual actions' do
given(:pipeline) { create(:ci_pipeline, project: project) }
given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
scenario 'does show a play button' do
expect(page).to have_link(manual.name.humanize)
end
scenario 'does allow to play manual action' do
expect(manual).to be_skipped
expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
expect(page).to have_content(manual.name)
expect(manual.reload).to be_pending
end
end
end
end
......@@ -54,6 +73,7 @@ feature 'Environments', feature: true do
describe 'when showing the environment' do
given(:environment) { create(:environment, project: project) }
given!(:deployment) { }
given!(:manual) { }
before do
visit namespace_project_environment_path(project.namespace, project, environment)
......@@ -77,7 +97,8 @@ feature 'Environments', feature: true do
end
context 'with build' do
given(:build) { create(:ci_build, project: project) }
given(:pipeline) { create(:ci_pipeline, project: project) }
given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
scenario 'does show build name' do
......@@ -87,6 +108,21 @@ feature 'Environments', feature: true do
scenario 'does show retry button' do
expect(page).to have_link('Retry')
end
context 'with manual action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
scenario 'does show a play button' do
expect(page).to have_link(manual.name.humanize)
end
scenario 'does allow to play manual action' do
expect(manual).to be_skipped
expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
expect(page).to have_content(manual.name)
expect(manual.reload).to be_pending
end
end
end
end
end
......
......@@ -62,6 +62,20 @@ describe "Pipelines" do
end
end
context 'with manual actions' do
let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'manual build', stage: 'test', commands: 'test') }
before { visit namespace_project_pipelines_path(project.namespace, project) }
it { expect(page).to have_link('Manual build') }
context 'when playing' do
before { click_link('Manual build') }
it { expect(manual.reload).to be_pending }
end
end
context 'for generic statuses' do
context 'when running' do
let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') }
......@@ -117,6 +131,7 @@ describe "Pipelines" do
@success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
@failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
@running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
@manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build')
@external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
end
......@@ -131,6 +146,7 @@ describe "Pipelines" do
expect(page).to have_content(@external.id)
expect(page).to have_content('Retry failed')
expect(page).to have_content('Cancel running')
expect(page).to have_link('Play')
end
context 'retrying builds' do
......@@ -154,6 +170,12 @@ describe "Pipelines" do
it { expect(page).to have_selector('.ci-canceled') }
end
end
context 'playing manual build' do
before { click_link('Play') }
it { expect(@manual.reload).to be_pending }
end
end
describe 'POST /:project/pipelines' do
......
......@@ -1141,7 +1141,7 @@ EOT
config = YAML.dump({ rspec: { script: "test", when: 1 } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure, always or manual")
end
it "returns errors if job artifacts:name is not an a string" do
......
......@@ -113,7 +113,7 @@ describe Gitlab::Badge::Build do
sha: sha,
ref: branch)
create(:ci_build, pipeline: pipeline)
create(:ci_build, pipeline: pipeline, stage: 'notify')
end
def status_node(data, status)
......
......@@ -2765,7 +2765,7 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
}
],
"st_diffs": [
"utf8_st_diffs": [
{
"diff": "Binary files a/.DS_Store and /dev/null differ\n",
"new_path": ".DS_Store",
......@@ -3138,7 +3138,7 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
}
],
"st_diffs": [
"utf8_st_diffs": [
{
"diff": "--- /dev/null\n+++ b/files/ruby/feature.rb\n@@ -0,0 +1,5 @@\n+class Feature\n+ def foo\n+ puts 'bar'\n+ end\n+end\n",
"new_path": "files/ruby/feature.rb",
......@@ -3423,7 +3423,7 @@
"committer_email": "james@jameslopez.es"
}
],
"st_diffs": [
"utf8_st_diffs": [
{
"diff": "--- /dev/null\n+++ b/test\n",
"new_path": "test",
......@@ -3960,7 +3960,7 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
}
],
"st_diffs": [
"utf8_st_diffs": [
{
"diff": "Binary files a/.DS_Store and /dev/null differ\n",
"new_path": ".DS_Store",
......@@ -4597,7 +4597,7 @@
"committer_email": "marmis85@gmail.com"
}
],
"st_diffs": [
"utf8_st_diffs": [
{
"diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n - Add edit file button to MergeRequest diff\n - Public groups (Jason Hollingsworth)\n",
"new_path": "CHANGELOG",
......@@ -5108,7 +5108,7 @@
"committer_email": "stanhu@packetzoom.com"
}
],
"st_diffs": [
"utf8_st_diffs": [
{
"diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n - Add edit file button to MergeRequest diff\n - Public groups (Jason Hollingsworth)\n",
"new_path": "CHANGELOG",
......@@ -5434,7 +5434,7 @@
"id": 11,
"state": "empty",
"st_commits": null,
"st_diffs": [
"utf8_st_diffs": [
],
"merge_request_id": 11,
......@@ -5961,7 +5961,7 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
}
],
"st_diffs": [
"utf8_st_diffs": [
{
"diff": "Binary files a/.DS_Store and /dev/null differ\n",
"new_path": ".DS_Store",
......@@ -6400,7 +6400,7 @@
"committer_email": "james@jameslopez.es"
}
],
"st_diffs": [
"utf8_st_diffs": [
{
"diff": "--- /dev/null\n+++ b/test\n",
"new_path": "test",
......
......@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
describe 'restore project tree' do
let(:user) { create(:user) }
let(:namespace) { create(:namespace, owner: user) }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
......@@ -53,6 +54,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(event.note.noteable.project).not_to be_nil
end
end
it 'has the correct data for merge request st_diffs' do
# makes sure we are renaming the custom method +utf8_st_diffs+ into +st_diffs+
expect { restored_project_json }.to change(MergeRequestDiff.where.not(st_diffs: nil), :count).by(9)
end
end
end
end
......@@ -102,12 +102,17 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
it 'has ci pipeline notes' do
expect(saved_project_json['pipelines'].first['notes']).not_to be_empty
end
it 'does not complain about non UTF-8 characters in MR diffs' do
ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'")
expect(project_tree_saver.save).to be true
end
end
end
def setup_project
issue = create(:issue, assignee: user)
merge_request = create(:merge_request)
label = create(:label)
snippet = create(:project_snippet)
release = create(:release)
......@@ -115,12 +120,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
project = create(:project,
:public,
issues: [issue],
merge_requests: [merge_request],
labels: [label],
snippets: [snippet],
releases: [release]
)
merge_request = create(:merge_request, source_project: project)
commit_status = create(:commit_status, project: project)
ci_pipeline = create(:ci_pipeline,
......
......@@ -191,16 +191,16 @@ describe Ci::Build, models: true do
end
describe '#variables' do
context 'returns variables' do
subject { build.variables }
let(:predefined_variables) do
[
{ key: :CI_BUILD_NAME, value: 'test', public: true },
{ key: :CI_BUILD_STAGE, value: 'test', public: true },
]
end
let(:predefined_variables) do
[
{ key: :CI_BUILD_NAME, value: 'test', public: true },
{ key: :CI_BUILD_STAGE, value: 'stage', public: true },
]
end
subject { build.variables }
context 'returns variables' do
let(:yaml_variables) do
[
{ key: :DB_NAME, value: 'postgres', public: true }
......@@ -208,7 +208,7 @@ describe Ci::Build, models: true do
end
before do
build.update_attributes(stage: 'stage', yaml_variables: yaml_variables)
build.yaml_variables = yaml_variables
end
it { is_expected.to eq(predefined_variables + yaml_variables) }
......@@ -262,6 +262,54 @@ describe Ci::Build, models: true do
end
end
end
context 'when yaml_variables is undefined' do
before do
build.yaml_variables = nil
end
context 'use from gitlab-ci.yml' do
before do
stub_ci_pipeline_yaml_file(config)
end
context 'if config is not found' do
let(:config) { nil }
it { is_expected.to eq(predefined_variables) }
end
context 'if config does not have a questioned job' do
let(:config) do
YAML.dump({
test_other: {
script: 'Hello World'
}
})
end
it { is_expected.to eq(predefined_variables) }
end
context 'if config has variables' do
let(:config) do
YAML.dump({
test: {
script: 'Hello World',
variables: {
KEY: 'value'
}
}
})
end
let(:variables) do
[{ key: :KEY, value: 'value', public: true }]
end
it { is_expected.to eq(predefined_variables + variables) }
end
end
end
end
describe '#has_tags?' do
......@@ -670,4 +718,120 @@ describe Ci::Build, models: true do
end
end
end
describe '#manual?' do
before do
build.update(when: value)
end
subject { build.manual? }
context 'when is set to manual' do
let(:value) { 'manual' }
it { is_expected.to be_truthy }
end
context 'when set to something else' do
let(:value) { 'something else' }
it { is_expected.to be_falsey }
end
end
describe '#other_actions' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') }
subject { build.other_actions }
it 'returns other actions' do
is_expected.to contain_exactly(other_build)
end
end
describe '#play' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
subject { build.play }
it 'enques a build' do
is_expected.to be_pending
is_expected.to eq(build)
end
context 'for success build' do
before { build.queue }
it 'creates a new build' do
is_expected.to be_pending
is_expected.not_to eq(build)
end
end
end
describe '#when' do
subject { build.when }
context 'if is undefined' do
before do
build.when = nil
end
context 'use from gitlab-ci.yml' do
before do
stub_ci_pipeline_yaml_file(config)
end
context 'if config is not found' do
let(:config) { nil }
it { is_expected.to eq('on_success') }
end
context 'if config does not have a questioned job' do
let(:config) do
YAML.dump({
test_other: {
script: 'Hello World'
}
})
end
it { is_expected.to eq('on_success') }
end
context 'if config has when' do
let(:config) do
YAML.dump({
test: {
script: 'Hello World',
when: 'always'
}
})
end
it { is_expected.to eq('always') }
end
end
end
end
describe '#retryable?' do
context 'when build is running' do
before { build.run! }
it 'should return false' do
expect(build.retryable?).to be false
end
end
context 'when build is finished' do
before { build.success! }
it 'should return true' do
expect(build.retryable?).to be true
end
end
end
end
......@@ -260,6 +260,68 @@ describe Ci::Pipeline, models: true do
expect(pipeline.reload.status).to eq('canceled')
end
end
context 'when listing manual actions' do
let(:yaml) do
{
stages: ["build", "test", "test_failure", "deploy", "cleanup"],
build: {
stage: "build",
script: "BUILD",
},
test: {
stage: "test",
script: "TEST",
},
test_failure: {
stage: "test_failure",
script: "ON test failure",
when: "on_failure",
},
deploy: {
stage: "deploy",
script: "PUBLISH",
},
production: {
stage: "deploy",
script: "PUBLISH",
when: "manual",
},
cleanup: {
stage: "cleanup",
script: "TIDY UP",
when: "always",
},
clear_cache: {
stage: "cleanup",
script: "CLEAR CACHE",
when: "manual",
}
}
end
it 'returns only for skipped builds' do
# currently all builds are created
expect(create_builds).to be_truthy
expect(manual_actions).to be_empty
# succeed stage build
pipeline.builds.running_or_pending.each(&:success)
expect(manual_actions).to be_empty
# succeed stage test
pipeline.builds.running_or_pending.each(&:success)
expect(manual_actions).to be_one # production
# succeed stage deploy
pipeline.builds.running_or_pending.each(&:success)
expect(manual_actions).to be_many # production and clear cache
end
def manual_actions
pipeline.manual_actions
end
end
end
context 'when no builds created' do
......@@ -416,4 +478,28 @@ describe Ci::Pipeline, models: true do
end
end
end
describe '#manual_actions' do
subject { pipeline.manual_actions }
it 'when none defined' do
is_expected.to be_empty
end
context 'when action defined' do
let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }
it 'returns one action' do
is_expected.to contain_exactly(manual)
end
context 'there are multiple of the same name' do
let!(:manual2) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }
it 'returns latest one' do
is_expected.to contain_exactly(manual2)
end
end
end
end
end
......@@ -11,6 +11,7 @@ describe Deployment, models: true do
it { is_expected.to delegate_method(:name).to(:environment).with_prefix }
it { is_expected.to delegate_method(:commit).to(:project) }
it { is_expected.to delegate_method(:commit_title).to(:commit).as(:try) }
it { is_expected.to delegate_method(:manual_actions).to(:deployable).as(:try) }
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to validate_presence_of(:sha) }
......
......@@ -211,4 +211,27 @@ describe API::Helpers, api: true do
expect(sudo_identifier).to eq(' 123')
end
end
describe '.to_boolean' do
it 'converts a valid string to a boolean' do
expect(to_boolean('true')).to be_truthy
expect(to_boolean('YeS')).to be_truthy
expect(to_boolean('t')).to be_truthy
expect(to_boolean('1')).to be_truthy
expect(to_boolean('ON')).to be_truthy
expect(to_boolean('FaLse')).to be_falsy
expect(to_boolean('F')).to be_falsy
expect(to_boolean('NO')).to be_falsy
expect(to_boolean('n')).to be_falsy
expect(to_boolean('0')).to be_falsy
expect(to_boolean('oFF')).to be_falsy
end
it 'converts an invalid string to nil' do
expect(to_boolean('fals')).to be_nil
expect(to_boolean('yeah')).to be_nil
expect(to_boolean('')).to be_nil
expect(to_boolean(nil)).to be_nil
end
end
end
......@@ -32,6 +32,8 @@ describe API::API, api: true do
expect(json_response['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(false)
expect(json_response['developers_can_push']).to eq(false)
expect(json_response['developers_can_merge']).to eq(false)
end
it "should return a 403 error if guest" do
......@@ -45,14 +47,95 @@ describe API::API, api: true do
end
end
describe "PUT /projects/:id/repository/branches/:branch/protect" do
it "should protect a single branch" do
describe 'PUT /projects/:id/repository/branches/:branch/protect' do
it 'protects a single branch' do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(false)
expect(json_response['developers_can_merge']).to eq(false)
end
it 'protects a single branch and developers can push' do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
developers_can_push: true
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(true)
expect(json_response['developers_can_merge']).to eq(false)
end
it 'protects a single branch and developers can merge' do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
developers_can_merge: true
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(false)
expect(json_response['developers_can_merge']).to eq(true)
end
it 'protects a single branch and developers can push and merge' do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
developers_can_push: true, developers_can_merge: true
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(true)
expect(json_response['developers_can_merge']).to eq(true)
end
it 'protects a single branch and developers cannot push and merge' do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
developers_can_push: 'tru', developers_can_merge: 'tr'
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(false)
expect(json_response['developers_can_merge']).to eq(false)
end
context 'on a protected branch' do
let(:protected_branch) { 'foo' }
before do
project.repository.add_branch(user, protected_branch, 'master')
create(:protected_branch, project: project, name: protected_branch, developers_can_push: true, developers_can_merge: true)
end
it 'updates that a developer can push' do
put api("/projects/#{project.id}/repository/branches/#{protected_branch}/protect", user),
developers_can_push: false, developers_can_merge: false
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(protected_branch)
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(false)
expect(json_response['developers_can_merge']).to eq(false)
end
it 'does not update that a developer can push' do
put api("/projects/#{project.id}/repository/branches/#{protected_branch}/protect", user),
developers_can_push: 'foobar', developers_can_merge: 'foo'
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(protected_branch)
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(true)
expect(json_response['developers_can_merge']).to eq(true)
end
end
it "should return a 404 error if branch not found" do
......
......@@ -134,8 +134,7 @@ describe API::Todos, api: true do
delete api('/todos', john_doe)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
expect(response.body).to eq('3')
expect(pending_1.reload).to be_done
expect(pending_2.reload).to be_done
expect(pending_3.reload).to be_done
......
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