Commit 39e6ef7e authored by Valery Sizov's avatar Valery Sizov

Merge branch 'ce_upstream' into 'master'

CE upstream



See merge request !570
parents 31a87ef8 cb77d0c0
Please view this file on the master branch, on stable branches it's out of date. 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) v 8.10.0 (unreleased)
- Fix profile activity heatmap to show correct day name (eanplatter) - Fix profile activity heatmap to show correct day name (eanplatter)
...@@ -35,12 +37,15 @@ v 8.10.0 (unreleased) ...@@ -35,12 +37,15 @@ v 8.10.0 (unreleased)
- Support U2F devices in Firefox. !5177 - Support U2F devices in Firefox. !5177
- Fix issue, preventing users w/o push access to sort tags !5105 (redetection) - Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
- Add Spring EmojiOne updates. - Add Spring EmojiOne updates.
- Added Rake task for tracking deployments !5320
- Fix fetching LFS objects for private CI projects - Fix fetching LFS objects for private CI projects
- Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237 - Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237
- Add syntax for multiline blockquote using `>>>` fence !3954 - Add syntax for multiline blockquote using `>>>` fence !3954
- Fix viewing notification settings when a project is pending deletion - Fix viewing notification settings when a project is pending deletion
- Updated compare dropdown menus to use GL dropdown - Updated compare dropdown menus to use GL dropdown
- Redirects back to issue after clicking login link
- Eager load award emoji on notes - 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) - 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 - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020
- Updated project header design - Updated project header design
...@@ -56,6 +61,7 @@ v 8.10.0 (unreleased) ...@@ -56,6 +61,7 @@ v 8.10.0 (unreleased)
- API: Expose `due_date` for issues (Robert Schilling) - API: Expose `due_date` for issues (Robert Schilling)
- API: Todos !3188 (Robert Schilling) - API: Todos !3188 (Robert Schilling)
- API: Expose shared groups for projects and shared projects for groups !5050 (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 - Add "Enabled Git access protocols" to Application Settings
- Diffs will create button/diff form on demand no on server side - Diffs will create button/diff form on demand no on server side
- Reduce size of HTML used by diff comment forms - Reduce size of HTML used by diff comment forms
...@@ -124,6 +130,8 @@ v 8.10.0 (unreleased) ...@@ -124,6 +130,8 @@ v 8.10.0 (unreleased)
- Allow empty repositories on project import/export - Allow empty repositories on project import/export
- Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska) - Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska)
- Allow bulk (un)subscription from issues in issue index - Allow bulk (un)subscription from issues in issue index
- Fix MR diff encoding issues exporting GitLab projects
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
v 8.9.6 v 8.9.6
- Fix importing of events under notes for GitLab projects. !5154 - Fix importing of events under notes for GitLab projects. !5154
......
GIT
remote: https://gitlab.com/gitlab-org/gitlab_git.git
revision: 0df0cdd9bb1164a7595f1f69f5dfa79489e3eaf1
branch: optional_branch_update
specs:
gitlab_git (10.3.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
rugged (~> 0.24.0)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
......
# GitLab Maintenance Policy # 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. The current stable release will receive security patches and bug fixes
- **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. (eg. `8.9.0` -> `8.9.1`). Feature releases will mark the next supported stable
- **Patch number**: When backwards compatible bug fixes are introduced that fix incorrect behavior. 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; ...@@ -16,7 +16,7 @@ $border-color: #e5e5e5;
$focus-border-color: #3aabf0; $focus-border-color: #3aabf0;
$table-border-color: #f0f0f0; $table-border-color: #f0f0f0;
$background-color: #fafafa; $background-color: #fafafa;
$dark-background-color: #f7f7f7; $dark-background-color: #f5f5f5;
$table-text-gray: #8f8f8f; $table-text-gray: #8f8f8f;
/* /*
......
...@@ -63,7 +63,7 @@ form.edit-issue { ...@@ -63,7 +63,7 @@ form.edit-issue {
.merge-request, .merge-request,
.issue { .issue {
&.today { &.today {
background: #f8feef; background: #f3fff2;
border-color: #e1e8d5; border-color: #e1e8d5;
} }
......
.pipelines { .pipelines {
.stage { .stage {
max-width: 80px; max-width: 90px;
width: 80px; width: 90px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
...@@ -30,13 +30,17 @@ ...@@ -30,13 +30,17 @@
} }
.table.builds { .table.builds {
min-width: 1100px; min-width: 1200px;
tr { tr {
th { th {
padding: 16px; padding: 16px 8px;
border: none; border: none;
} }
td {
padding: 10px 8px;
}
} }
tbody { tbody {
...@@ -53,9 +57,8 @@ ...@@ -53,9 +57,8 @@
.branch-commit { .branch-commit {
.branch-name { .branch-name {
margin-left: 8px;
font-weight: bold; font-weight: bold;
max-width: 180px; max-width: 150px;
overflow: hidden; overflow: hidden;
display: inline-block; display: inline-block;
white-space: nowrap; white-space: nowrap;
...@@ -64,10 +67,15 @@ ...@@ -64,10 +67,15 @@
} }
svg { svg {
margin: 0 6px;
height: 14px; height: 14px;
width: auto; width: auto;
vertical-align: middle; vertical-align: middle;
fill: $table-text-gray;
}
.fa {
font-size: 12px;
color: $table-text-gray;
} }
.commit-id { .commit-id {
...@@ -100,6 +108,22 @@ ...@@ -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, .duration,
.finished-at { .finished-at {
color: $table-text-gray; color: $table-text-gray;
...@@ -107,21 +131,19 @@ ...@@ -107,21 +131,19 @@
.fa { .fa {
font-size: 12px; font-size: 12px;
margin-right: 4px;
} }
svg { svg {
height: 12px; width: 12px;
width: auto; height: auto;
vertical-align: middle; vertical-align: middle;
} margin-right: 4px;
.fa,
svg {
margin-right: 5px;
} }
} }
.pipeline-actions { .pipeline-actions {
min-width: 140px;
.btn { .btn {
margin: 0; margin: 0;
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
border-top: 1px solid $table-border-gray; border-top: 1px solid $table-border-gray;
td, th { td, th {
line-height: 23px; line-height: 21px;
} }
&:hover { &:hover {
......
class Projects::BuildsController < Projects::ApplicationController class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all] 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] before_action :authorize_update_build!, except: [:index, :show, :status, :raw]
layout 'project' layout 'project'
...@@ -49,14 +49,19 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -49,14 +49,19 @@ class Projects::BuildsController < Projects::ApplicationController
end end
def retry def retry
unless @build.retryable? return render_404 unless @build.retryable?
return render_404
end
build = Ci::Build.retry(@build, current_user) build = Ci::Build.retry(@build, current_user)
redirect_to build_path(build) redirect_to build_path(build)
end end
def play
return render_404 unless @build.playable?
build = @build.play(current_user)
redirect_to build_path(build)
end
def cancel def cancel
@build.cancel @build.cancel
redirect_to build_path(@build) redirect_to build_path(@build)
......
...@@ -15,6 +15,7 @@ module Ci ...@@ -15,6 +15,7 @@ module Ci
scope :with_artifacts, ->() { where.not(artifacts_file: nil) } scope :with_artifacts, ->() { where.not(artifacts_file: nil) }
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
scope :manual_actions, ->() { where(when: :manual) }
mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader
...@@ -91,6 +92,29 @@ module Ci ...@@ -91,6 +92,29 @@ module Ci
end end
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? def retryable?
project.builds_enabled? && commands.present? && complete? project.builds_enabled? && commands.present? && complete?
end end
...@@ -121,12 +145,7 @@ module Ci ...@@ -121,12 +145,7 @@ module Ci
end end
def variables def variables
variables = [] predefined_variables + yaml_variables + project_variables + trigger_variables
variables += predefined_variables
variables += yaml_variables if yaml_variables
variables += project_variables
variables += trigger_variables
variables
end end
def merge_request def merge_request
...@@ -386,6 +405,14 @@ module Ci ...@@ -386,6 +405,14 @@ module Ci
self.update(artifacts_expire_at: nil) self.update(artifacts_expire_at: nil)
end 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 private
def update_artifacts_size def update_artifacts_size
...@@ -428,5 +455,11 @@ module Ci ...@@ -428,5 +455,11 @@ module Ci
variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request
variables variables
end end
def build_attributes_from_config
return {} unless pipeline.config_processor
pipeline.config_processor.build_attributes(name)
end
end end
end end
...@@ -69,6 +69,10 @@ module Ci ...@@ -69,6 +69,10 @@ module Ci
!tag? !tag?
end end
def manual_actions
builds.latest.manual_actions
end
def retryable? def retryable?
builds.latest.any? do |build| builds.latest.any? do |build|
build.failed? && build.retryable? build.failed? && build.retryable?
......
...@@ -22,6 +22,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -22,6 +22,10 @@ class CommitStatus < ActiveRecord::Base
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
state_machine :status, initial: :pending do state_machine :status, initial: :pending do
event :queue do
transition skipped: :pending
end
event :run do event :run do
transition pending: :running transition pending: :running
end end
......
...@@ -16,10 +16,10 @@ module Statuseable ...@@ -16,10 +16,10 @@ module Statuseable
deduce_status = "(CASE deduce_status = "(CASE
WHEN (#{builds})=0 THEN NULL 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})=(#{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' WHEN (#{running})+(#{pending})>0 THEN 'running'
ELSE 'failed' ELSE 'failed'
END)" END)"
......
...@@ -32,4 +32,8 @@ class Deployment < ActiveRecord::Base ...@@ -32,4 +32,8 @@ class Deployment < ActiveRecord::Base
def keep_around_commit def keep_around_commit
project.repository.keep_around(self.sha) project.repository.keep_around(self.sha)
end end
def manual_actions
deployable.try(:other_actions)
end
end end
class MergeRequestDiff < ActiveRecord::Base class MergeRequestDiff < ActiveRecord::Base
include Sortable include Sortable
include Importable include Importable
include EncodingHelper
# Prevent store of diff if commits amount more then 500 # Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 100 COMMITS_SAFE_SIZE = 100
...@@ -211,6 +212,14 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -211,6 +212,14 @@ class MergeRequestDiff < ActiveRecord::Base
branch_base_commit.try(:sha) branch_base_commit.try(:sha)
end 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 # #save or #update_attributes providing changes on serialized attributes do a lot of
# serialization and deserialization calls resulting in bad performance. # serialization and deserialization calls resulting in bad performance.
......
...@@ -783,6 +783,7 @@ class Repository ...@@ -783,6 +783,7 @@ class Repository
options[:commit] = { options[:commit] = {
message: message, message: message,
branch: ref, branch: ref,
update_ref: false,
} }
raw_repository.mkdir(path, options) raw_repository.mkdir(path, options)
...@@ -798,6 +799,7 @@ class Repository ...@@ -798,6 +799,7 @@ class Repository
options[:commit] = { options[:commit] = {
message: message, message: message,
branch: ref, branch: ref,
update_ref: false,
} }
options[:file] = { options[:file] = {
...@@ -818,7 +820,8 @@ class Repository ...@@ -818,7 +820,8 @@ class Repository
options[:author] = committer options[:author] = committer
options[:commit] = { options[:commit] = {
message: message, message: message,
branch: ref branch: ref,
update_ref: false,
} }
options[:file] = { options[:file] = {
...@@ -855,7 +858,7 @@ class Repository ...@@ -855,7 +858,7 @@ class Repository
raise "Invalid merge target" if our_commit.nil? raise "Invalid merge target" if our_commit.nil?
raise "Invalid merge source" if their_commit.nil? raise "Invalid merge source" if their_commit.nil?
commit_with_hooks(user, target_branch) do |ref| commit_with_hooks(user, target_branch) do
source_sha source_sha
end end
end end
...@@ -870,11 +873,10 @@ class Repository ...@@ -870,11 +873,10 @@ class Repository
merge_index = rugged.merge_commits(our_commit, their_commit) merge_index = rugged.merge_commits(our_commit, their_commit)
return false if merge_index.conflicts? 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( actual_options = options.merge(
parents: [our_commit, their_commit], parents: [our_commit, their_commit],
tree: merge_index.write_tree(rugged), tree: merge_index.write_tree(rugged),
update_ref: tmp_ref
) )
commit_id = Rugged::Commit.create(rugged, actual_options) commit_id = Rugged::Commit.create(rugged, actual_options)
...@@ -889,15 +891,14 @@ class Repository ...@@ -889,15 +891,14 @@ class Repository
return false unless revert_tree_id 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) committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged, source_sha = Rugged::Commit.create(rugged,
message: commit.revert_message, message: commit.revert_message,
author: committer, author: committer,
committer: committer, committer: committer,
tree: revert_tree_id, tree: revert_tree_id,
parents: [rugged.lookup(source_sha)], parents: [rugged.lookup(source_sha)])
update_ref: ref)
end end
end end
...@@ -907,7 +908,7 @@ class Repository ...@@ -907,7 +908,7 @@ class Repository
return false unless cherry_pick_tree_id 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) committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged, source_sha = Rugged::Commit.create(rugged,
message: commit.message, message: commit.message,
...@@ -918,8 +919,7 @@ class Repository ...@@ -918,8 +919,7 @@ class Repository
}, },
committer: committer, committer: committer,
tree: cherry_pick_tree_id, tree: cherry_pick_tree_id,
parents: [rugged.lookup(source_sha)], parents: [rugged.lookup(source_sha)])
update_ref: ref)
end end
end end
...@@ -1119,20 +1119,6 @@ class Repository ...@@ -1119,20 +1119,6 @@ class Repository
Gitlab::Popen.popen(args, path_to_repo) Gitlab::Popen.popen(args, path_to_repo)
end 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) def commit_with_hooks(current_user, branch)
update_autocrlf_option update_autocrlf_option
...@@ -1145,33 +1131,31 @@ class Repository ...@@ -1145,33 +1131,31 @@ class Repository
oldrev = target_branch.target oldrev = target_branch.target
end end
with_tmp_ref(oldrev) do |tmp_ref| # Make commit
# Make commit in tmp ref newrev = yield(ref)
newrev = yield(tmp_ref)
unless newrev unless newrev
raise CommitError.new('Failed to create commit') raise CommitError.new('Failed to create commit')
end 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 # Make sure target branch was not changed during pre-receive hook
if was_empty || !target_branch if current_head == oldrev
# Create branch rugged.references.update(ref, newrev)
rugged.references.create(ref, newrev)
else else
# Update head raise CommitError.new('Commit was rejected because branch received new push')
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
end end
end end
newrev
end end
newrev
end end
def ls_files(ref) def ls_files(ref)
......
...@@ -15,7 +15,7 @@ module Ci ...@@ -15,7 +15,7 @@ module Ci
status == 'success' status == 'success'
when 'on_failure' when 'on_failure'
status == 'failed' status == 'failed'
when 'always' when 'always', 'manual'
%w(success failed).include?(status) %w(success failed).include?(status)
end end
end end
...@@ -47,6 +47,10 @@ module Ci ...@@ -47,6 +47,10 @@ module Ci
user: user, user: user,
project: @pipeline.project) 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. # We do not persist new builds here.
# Those will be persisted when @pipeline is saved. # Those will be persisted when @pipeline is saved.
......
...@@ -15,21 +15,19 @@ class CreateBranchService < BaseService ...@@ -15,21 +15,19 @@ class CreateBranchService < BaseService
return error('Branch already exists') return error('Branch already exists')
end end
new_branch = nil new_branch = if source_project != @project
repository.fetch_ref(
if source_project != @project source_project.repository.path_to_repo,
repository.with_tmp_ref do |tmp_ref| "refs/heads/#{ref}",
repository.fetch_ref( "refs/heads/#{branch_name}"
source_project.repository.path_to_repo, )
"refs/heads/#{ref}",
tmp_ref repository.after_create_branch
)
repository.find_branch(branch_name)
new_branch = repository.add_branch(current_user, branch_name, tmp_ref) else
end repository.add_branch(current_user, branch_name, ref)
else end
new_branch = repository.add_branch(current_user, branch_name, ref)
end
if new_branch if new_branch
success(new_branch) success(new_branch)
......
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,8 @@
%span.label.label-danger allowed to fail %span.label.label-danger allowed to fail
- if defined?(retried) && retried - if defined?(retried) && retried
%span.label.label-warning retried %span.label.label-warning retried
- if build.manual?
%span.label.label-info manual
- if defined?(runner) && runner - if defined?(runner) && runner
...@@ -79,6 +81,11 @@ ...@@ -79,6 +81,11 @@
- if build.active? - 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 = 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') = icon('remove', class: 'cred')
- elsif defined?(allow_retry) && allow_retry && build.retryable? - elsif defined?(allow_retry) && allow_retry
= 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 - if build.retryable?
= icon('repeat') = 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 @@ ...@@ -10,12 +10,13 @@
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
%span ##{pipeline.id} %span ##{pipeline.id}
- if pipeline.ref - 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" = 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" = link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace"
- if pipeline.tag? - if pipeline.latest?
%span.label.label-primary tag
- elsif pipeline.latest?
%span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
- if pipeline.triggered? - if pipeline.triggered?
%span.label.label-primary triggered %span.label.label-primary triggered
...@@ -57,18 +58,31 @@ ...@@ -57,18 +58,31 @@
%td.pipeline-actions %td.pipeline-actions
.controls.hidden-xs.pull-right .controls.hidden-xs.pull-right
- artifacts = pipeline.builds.latest.select { |b| b.artifacts? } - artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
- if artifacts.present? - actions = pipeline.manual_actions
.inline - if artifacts.present? || actions.any?
.btn-group .btn-group.inline
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'} - if actions.any?
= icon("download") .btn-group
%b.caret %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
%ul.dropdown-menu.dropdown-menu-align-right = icon("play")
- artifacts.each do |build| %b.caret
%li %ul.dropdown-menu.dropdown-menu-align-right
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do - actions.each do |build|
= icon("download") %li
%span Download '#{build.name}' artifacts = 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) - if can?(current_user, :update_pipeline, @project)
.cancel-retry-btns.inline .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 @@ ...@@ -7,17 +7,11 @@
%td %td
- if deployment.deployable - 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})" = "#{deployment.deployable.name} (##{deployment.deployable.id})"
%td %td
#{time_ago_with_tooltip(deployment.created_at)} #{time_ago_with_tooltip(deployment.created_at)}
%td %td
- if can?(current_user, :create_deployment, deployment) && deployment.deployable = render 'projects/deployments/actions', deployment: deployment, allow_rollback: true
.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
...@@ -15,3 +15,6 @@ ...@@ -15,3 +15,6 @@
%td %td
- if last_deployment - if last_deployment
#{time_ago_with_tooltip(last_deployment.created_at)} #{time_ago_with_tooltip(last_deployment.created_at)}
%td
= render 'projects/deployments/actions', deployment: last_deployment
...@@ -28,4 +28,5 @@ ...@@ -28,4 +28,5 @@
%th Environment %th Environment
%th Last deployment %th Last deployment
%th Date %th Date
%th
= render @environments = render @environments
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
%div{ class: container_class } %div{ class: container_class }
.top-area .top-area
.col-md-9 .col-md-9
%h3.page-title= @environment.name.titleize %h3.page-title= @environment.name.capitalize
.col-md-3 .col-md-3
.nav-controls .nav-controls
......
...@@ -14,9 +14,9 @@ ...@@ -14,9 +14,9 @@
.disabled-comment.text-center .disabled-comment.text-center
.disabled-comment-text.inline .disabled-comment-text.inline
Please Please
= link_to "register",new_user_session_path = link_to "register", new_session_path(:user, redirect_to_referer: 'yes')
or or
= link_to "login",new_user_session_path = link_to "login", new_session_path(:user, redirect_to_referer: 'yes')
to post a comment to post a comment
:javascript :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 ...@@ -811,6 +811,7 @@ Rails.application.routes.draw do
get :status get :status
post :cancel post :cancel
post :retry post :retry
post :play
post :erase post :erase
get :trace get :trace
get :raw get :raw
......
class Gitlab::Seeder::Builds class Gitlab::Seeder::Builds
STAGES = %w[build notify_build test notify_test deploy notify_deploy]
def initialize(project) def initialize(project)
@project = project @project = project
end end
def seed! def seed!
ci_commits.each do |ci_commit| pipelines.each do |pipeline|
begin begin
build_create!(ci_commit, name: 'test build 1') build_create!(pipeline, name: 'build:linux', stage: 'build')
build_create!(ci_commit, status: 'success', name: 'test build 2') 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 '.' print '.'
rescue ActiveRecord::RecordInvalid rescue ActiveRecord::RecordInvalid
print 'F' print 'F'
...@@ -15,8 +36,8 @@ class Gitlab::Seeder::Builds ...@@ -15,8 +36,8 @@ class Gitlab::Seeder::Builds
end end
end end
def ci_commits def pipelines
commits = @project.repository.commits('master', path: nil, limit: 5) commits = @project.repository.commits('master', limit: 5)
commits_sha = commits.map { |commit| commit.raw.id } commits_sha = commits.map { |commit| commit.raw.id }
commits_sha.map do |sha| commits_sha.map do |sha|
@project.ensure_pipeline(sha, 'master') @project.ensure_pipeline(sha, 'master')
...@@ -25,11 +46,11 @@ class Gitlab::Seeder::Builds ...@@ -25,11 +46,11 @@ class Gitlab::Seeder::Builds
[] []
end end
def build_create!(ci_commit, opts = {}) def build_create!(pipeline, opts = {})
attributes = build_attributes_for(ci_commit).merge(opts) attributes = build_attributes_for(pipeline, opts)
build = Ci::Build.new(attributes) 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| artifacts_cache_file(artifacts_archive_path) do |file|
build.artifacts_file = file build.artifacts_file = file
end end
...@@ -40,19 +61,28 @@ class Gitlab::Seeder::Builds ...@@ -40,19 +61,28 @@ class Gitlab::Seeder::Builds
end end
build.save! build.save!
build.update(status: build_status)
if %w(running success failed).include?(build.status) if %w(running success failed).include?(build.status)
# We need to set build trace after saving a build (id required) # We need to set build trace after saving a build (id required)
build.trace = FFaker::Lorem.paragraphs(6).join("\n\n") build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
end end
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) def build_attributes_for(pipeline, opts)
{ name: 'test build', commands: "$ build command", commit_status_attributes_for(pipeline, opts).merge(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 }
end end
def build_user def build_user
...@@ -63,13 +93,16 @@ class Gitlab::Seeder::Builds ...@@ -63,13 +93,16 @@ class Gitlab::Seeder::Builds
Ci::Build::AVAILABLE_STATUSES.sample Ci::Build::AVAILABLE_STATUSES.sample
end end
def stage_index(stage)
STAGES.index(stage) || 0
end
def artifacts_archive_path def artifacts_archive_path
Rails.root + 'spec/fixtures/ci_build_artifacts.zip' Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
end end
def artifacts_metadata_path def artifacts_metadata_path
Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz' Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz'
end end
def artifacts_cache_file(file_path) def artifacts_cache_file(file_path)
......
...@@ -23,6 +23,8 @@ Example response: ...@@ -23,6 +23,8 @@ Example response:
{ {
"name": "master", "name": "master",
"protected": true, "protected": true,
"developers_can_push": false,
"developers_can_merge": false,
"commit": { "commit": {
"author_email": "john@example.com", "author_email": "john@example.com",
"author_name": "John Smith", "author_name": "John Smith",
...@@ -64,6 +66,8 @@ Example response: ...@@ -64,6 +66,8 @@ Example response:
{ {
"name": "master", "name": "master",
"protected": true, "protected": true,
"developers_can_push": false,
"developers_can_merge": false,
"commit": { "commit": {
"author_email": "john@example.com", "author_email": "john@example.com",
"author_name": "John Smith", "author_name": "John Smith",
...@@ -91,13 +95,15 @@ PUT /projects/:id/repository/branches/:branch/protect ...@@ -91,13 +95,15 @@ PUT /projects/:id/repository/branches/:branch/protect
``` ```
```bash ```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 | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `branch` | string | yes | The name of the branch | | `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: Example response:
...@@ -117,7 +123,9 @@ Example response: ...@@ -117,7 +123,9 @@ Example response:
] ]
}, },
"name": "master", "name": "master",
"protected": true "protected": true,
"developers_can_push": true,
"developers_can_merge": true
} }
``` ```
...@@ -158,7 +166,9 @@ Example response: ...@@ -158,7 +166,9 @@ Example response:
] ]
}, },
"name": "master", "name": "master",
"protected": false "protected": false,
"developers_can_push": false,
"developers_can_merge": false
} }
``` ```
...@@ -196,7 +206,9 @@ Example response: ...@@ -196,7 +206,9 @@ Example response:
] ]
}, },
"name": "newbranch", "name": "newbranch",
"protected": false "protected": false,
"developers_can_push": false,
"developers_can_merge": false
} }
``` ```
......
...@@ -277,8 +277,7 @@ Example Response: ...@@ -277,8 +277,7 @@ Example Response:
## Mark all todos as done ## Mark all todos as done
Marks all pending todos for the current user as done. All todos marked as done Marks all pending todos for the current user as done. It returns the number of marked todos.
are returned in the response.
``` ```
DELETE /todos DELETE /todos
...@@ -291,154 +290,7 @@ curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.c ...@@ -291,154 +290,7 @@ curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.c
Example Response: Example Response:
```json ```json
[ 3
{
"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"
},
]
``` ```
[ce-3188]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3188 [ce-3188]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3188
...@@ -485,6 +485,7 @@ failure. ...@@ -485,6 +485,7 @@ failure.
1. `on_failure` - execute build only when at least one build from prior stages 1. `on_failure` - execute build only when at least one build from prior stages
fails. fails.
1. `always` - execute build regardless of the status of builds from prior stages. 1. `always` - execute build regardless of the status of builds from prior stages.
1. `manual` - execute build manually.
For example: For example:
...@@ -516,6 +517,7 @@ deploy_job: ...@@ -516,6 +517,7 @@ deploy_job:
stage: deploy stage: deploy
script: script:
- make deploy - make deploy
when: manual
cleanup_job: cleanup_job:
stage: cleanup stage: cleanup
...@@ -527,7 +529,20 @@ cleanup_job: ...@@ -527,7 +529,20 @@ cleanup_job:
The above script will: The above script will:
1. Execute `cleanup_build_job` only when `build_job` fails 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 ### environment
...@@ -757,12 +772,13 @@ Introduced in GitLab 8.6 and GitLab Runner v1.1.1. ...@@ -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 This feature should be used in conjunction with [`artifacts`](#artifacts) and
allows you to define the artifacts to pass between different builds. 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 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. 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. 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. 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 ...@@ -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 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 rake assets:precompile on the production machine. If you suspect that assets
have been corrupted, you should reinstall the omnibus package. 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 ...@@ -15,7 +15,8 @@ module API
# GET /projects/:id/repository/branches # GET /projects/:id/repository/branches
get ":id/repository/branches" do get ":id/repository/branches" do
branches = user_project.repository.branches.sort_by(&:name) 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 end
# Get a single branch # Get a single branch
...@@ -28,7 +29,8 @@ module API ...@@ -28,7 +29,8 @@ module API
get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do
@branch = user_project.repository.branches.find { |item| item.name == params[:branch] } @branch = user_project.repository.branches.find { |item| item.name == params[:branch] }
not_found!("Branch") unless @branch not_found!("Branch") unless @branch
present @branch, with: Entities::RepoObject, project: user_project
present @branch, with: Entities::RepoBranch, project: user_project
end end
# Protect a single branch # Protect a single branch
...@@ -36,6 +38,8 @@ module API ...@@ -36,6 +38,8 @@ module API
# Parameters: # Parameters:
# id (required) - The ID of a project # id (required) - The ID of a project
# branch (required) - The name of the branch # 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: # Example Request:
# PUT /projects/:id/repository/branches/:branch/protect # PUT /projects/:id/repository/branches/:branch/protect
put ':id/repository/branches/:branch/protect', put ':id/repository/branches/:branch/protect',
...@@ -43,11 +47,22 @@ module API ...@@ -43,11 +47,22 @@ module API
authorize_admin_project authorize_admin_project
@branch = user_project.repository.find_branch(params[:branch]) @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) 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 end
# Unprotect a single branch # Unprotect a single branch
...@@ -66,7 +81,7 @@ module API ...@@ -66,7 +81,7 @@ module API
protected_branch = user_project.protected_branches.find_by(name: @branch.name) protected_branch = user_project.protected_branches.find_by(name: @branch.name)
protected_branch.destroy if protected_branch protected_branch.destroy if protected_branch
present @branch, with: Entities::RepoObject, project: user_project present @branch, with: Entities::RepoBranch, project: user_project
end end
# Create branch # Create branch
...@@ -84,7 +99,7 @@ module API ...@@ -84,7 +99,7 @@ module API
if result[:status] == :success if result[:status] == :success
present result[:branch], present result[:branch],
with: Entities::RepoObject, with: Entities::RepoBranch,
project: user_project project: user_project
else else
render_api_error!(result[:message], 400) render_api_error!(result[:message], 400)
......
...@@ -129,21 +129,23 @@ module API ...@@ -129,21 +129,23 @@ module API
end end
end end
class RepoObject < Grape::Entity class RepoBranch < Grape::Entity
expose :name expose :name
expose :commit do |repo_obj, options| expose :commit do |repo_branch, options|
if repo_obj.respond_to?(:commit) options[:project].repository.commit(repo_branch.target)
repo_obj.commit
elsif options[:project]
options[:project].repository.commit(repo_obj.target)
end
end end
expose :protected do |repo, options| expose :protected do |repo_branch, options|
if options[:project] options[:project].protected_branch? repo_branch.name
options[:project].protected_branch? repo.name end
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
end end
...@@ -456,27 +458,14 @@ module API ...@@ -456,27 +458,14 @@ module API
end end
class RepoTag < Grape::Entity class RepoTag < Grape::Entity
expose :name expose :name, :message
expose :message do |repo_obj, _options|
if repo_obj.respond_to?(:message)
repo_obj.message
else
nil
end
end
expose :commit do |repo_obj, options| expose :commit do |repo_tag, options|
if repo_obj.respond_to?(:commit) options[:project].repository.commit(repo_tag.target)
repo_obj.commit
elsif options[:project]
options[:project].repository.commit(repo_obj.target)
end
end end
expose :release, using: Entities::Release do |repo_obj, options| expose :release, using: Entities::Release do |repo_tag, options|
if options[:project] options[:project].releases.find_by(tag: repo_tag.name)
options[:project].releases.find_by(tag: repo_obj.name)
end
end end
end end
......
...@@ -9,6 +9,13 @@ module API ...@@ -9,6 +9,13 @@ module API
[ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(value) [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(value)
end 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 def find_user_by_private_token
token_string = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s 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) User.find_by_authentication_token(token_string) || User.find_by_personal_access_token(token_string)
......
...@@ -75,7 +75,7 @@ module API ...@@ -75,7 +75,7 @@ module API
todos = find_todos todos = find_todos
todos.each(&:done) todos.each(&:done)
present paginate(Kaminari.paginate_array(todos)), with: Entities::Todo, current_user: current_user todos.length
end end
end end
end end
......
...@@ -44,23 +44,51 @@ module Ci ...@@ -44,23 +44,51 @@ module Ci
end end
def builds_for_ref(ref, tag = false, trigger_request = nil) def builds_for_ref(ref, tag = false, trigger_request = nil)
jobs_for_ref(ref, tag, trigger_request).map do |name, job| jobs_for_ref(ref, tag, trigger_request).map do |name, _|
build_job(name, job) build_attributes(name)
end end
end end
def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil) 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| jobs_for_stage_and_ref(stage, ref, tag, trigger_request).map do |name, _|
build_job(name, job) build_attributes(name)
end end
end end
def builds def builds
@jobs.map do |name, job| @jobs.map do |name, _|
build_job(name, job) build_attributes(name)
end end
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 private
def initial_parsing def initial_parsing
...@@ -89,33 +117,6 @@ module Ci ...@@ -89,33 +117,6 @@ module Ci
@jobs[name] = { stage: stage }.merge(job) @jobs[name] = { stage: stage }.merge(job)
end 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) def yaml_variables(name)
variables = global_variables.merge(job_variables(name)) variables = global_variables.merge(job_variables(name))
variables.map do |key, value| variables.map do |key, value|
...@@ -194,8 +195,8 @@ module Ci ...@@ -194,8 +195,8 @@ module Ci
raise ValidationError, "#{name} job: allow_failure parameter should be an boolean" raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
end end
if job[:when] && !job[:when].in?(%w[on_success on_failure 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 or always" raise ValidationError, "#{name} job: when parameter should be on_success, on_failure, always or manual"
end end
if job[:environment] && !validate_environment(job[:environment]) if job[:environment] && !validate_environment(job[:environment])
......
...@@ -2,7 +2,7 @@ module Gitlab ...@@ -2,7 +2,7 @@ module Gitlab
module ImportExport module ImportExport
extend self extend self
VERSION = '0.1.1' VERSION = '0.1.2'
FILENAME_LIMIT = 50 FILENAME_LIMIT = 50
def export_path(relative_path:) def export_path(relative_path:)
......
...@@ -53,7 +53,11 @@ included_attributes: ...@@ -53,7 +53,11 @@ included_attributes:
excluded_attributes: excluded_attributes:
snippets: snippets:
- :expired_at - :expired_at
merge_request_diff:
- :st_diffs
methods: methods:
statuses: statuses:
- :type - :type
\ No newline at end of file merge_request_diff:
- :utf8_st_diffs
\ No newline at end of file
...@@ -33,6 +33,7 @@ module Gitlab ...@@ -33,6 +33,7 @@ module Gitlab
update_project_references update_project_references
reset_ci_tokens if @relation_name == 'Ci::Trigger' reset_ci_tokens if @relation_name == 'Ci::Trigger'
@relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data'] @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 generate_imported_object
end end
...@@ -129,6 +130,10 @@ module Gitlab ...@@ -129,6 +130,10 @@ module Gitlab
def parsed_relation_hash def parsed_relation_hash
@relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) } @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
end end
def set_st_diffs
@relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs')
end
end end
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 ...@@ -3,6 +3,8 @@ include ActionDispatch::TestProcess
FactoryGirl.define do FactoryGirl.define do
factory :ci_build, class: Ci::Build do factory :ci_build, class: Ci::Build do
name 'test' name 'test'
stage 'test'
stage_idx 0
ref 'master' ref 'master'
tag false tag false
created_at 'Di 29. Okt 09:50:00 CET 2013' created_at 'Di 29. Okt 09:50:00 CET 2013'
...@@ -43,6 +45,11 @@ FactoryGirl.define do ...@@ -43,6 +45,11 @@ FactoryGirl.define do
status 'pending' status 'pending'
end end
trait :manual do
status 'skipped'
self.when 'manual'
end
trait :allowed_to_fail do trait :allowed_to_fail do
allow_failure true allow_failure true
end end
......
...@@ -13,6 +13,7 @@ feature 'Environments', feature: true do ...@@ -13,6 +13,7 @@ feature 'Environments', feature: true do
describe 'when showing environments' do describe 'when showing environments' do
given!(:environment) { } given!(:environment) { }
given!(:deployment) { } given!(:deployment) { }
given!(:manual) { }
before do before do
visit namespace_project_environments_path(project.namespace, project) visit namespace_project_environments_path(project.namespace, project)
...@@ -43,6 +44,24 @@ feature 'Environments', feature: true do ...@@ -43,6 +44,24 @@ feature 'Environments', feature: true do
scenario 'does show deployment SHA' do scenario 'does show deployment SHA' do
expect(page).to have_link(deployment.short_sha) expect(page).to have_link(deployment.short_sha)
end 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
end end
...@@ -54,6 +73,7 @@ feature 'Environments', feature: true do ...@@ -54,6 +73,7 @@ feature 'Environments', feature: true do
describe 'when showing the environment' do describe 'when showing the environment' do
given(:environment) { create(:environment, project: project) } given(:environment) { create(:environment, project: project) }
given!(:deployment) { } given!(:deployment) { }
given!(:manual) { }
before do before do
visit namespace_project_environment_path(project.namespace, project, environment) visit namespace_project_environment_path(project.namespace, project, environment)
...@@ -77,7 +97,8 @@ feature 'Environments', feature: true do ...@@ -77,7 +97,8 @@ feature 'Environments', feature: true do
end end
context 'with build' do 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) } given(:deployment) { create(:deployment, environment: environment, deployable: build) }
scenario 'does show build name' do scenario 'does show build name' do
...@@ -87,6 +108,21 @@ feature 'Environments', feature: true do ...@@ -87,6 +108,21 @@ feature 'Environments', feature: true do
scenario 'does show retry button' do scenario 'does show retry button' do
expect(page).to have_link('Retry') expect(page).to have_link('Retry')
end 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 end
end end
......
...@@ -62,6 +62,20 @@ describe "Pipelines" do ...@@ -62,6 +62,20 @@ describe "Pipelines" do
end end
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 'for generic statuses' do
context 'when running' do context 'when running' do
let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') } let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') }
...@@ -117,6 +131,7 @@ describe "Pipelines" do ...@@ -117,6 +131,7 @@ describe "Pipelines" do
@success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
@failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
@running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') @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') @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
end end
...@@ -131,6 +146,7 @@ describe "Pipelines" do ...@@ -131,6 +146,7 @@ describe "Pipelines" do
expect(page).to have_content(@external.id) expect(page).to have_content(@external.id)
expect(page).to have_content('Retry failed') expect(page).to have_content('Retry failed')
expect(page).to have_content('Cancel running') expect(page).to have_content('Cancel running')
expect(page).to have_link('Play')
end end
context 'retrying builds' do context 'retrying builds' do
...@@ -154,6 +170,12 @@ describe "Pipelines" do ...@@ -154,6 +170,12 @@ describe "Pipelines" do
it { expect(page).to have_selector('.ci-canceled') } it { expect(page).to have_selector('.ci-canceled') }
end end
end end
context 'playing manual build' do
before { click_link('Play') }
it { expect(@manual.reload).to be_pending }
end
end end
describe 'POST /:project/pipelines' do describe 'POST /:project/pipelines' do
......
...@@ -1141,7 +1141,7 @@ EOT ...@@ -1141,7 +1141,7 @@ EOT
config = YAML.dump({ rspec: { script: "test", when: 1 } }) config = YAML.dump({ rspec: { script: "test", when: 1 } })
expect do expect do
GitlabCiYamlProcessor.new(config, path) 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 end
it "returns errors if job artifacts:name is not an a string" do it "returns errors if job artifacts:name is not an a string" do
......
...@@ -113,7 +113,7 @@ describe Gitlab::Badge::Build do ...@@ -113,7 +113,7 @@ describe Gitlab::Badge::Build do
sha: sha, sha: sha,
ref: branch) ref: branch)
create(:ci_build, pipeline: pipeline) create(:ci_build, pipeline: pipeline, stage: 'notify')
end end
def status_node(data, status) def status_node(data, status)
......
...@@ -2765,7 +2765,7 @@ ...@@ -2765,7 +2765,7 @@
"committer_email": "dmitriy.zaporozhets@gmail.com" "committer_email": "dmitriy.zaporozhets@gmail.com"
} }
], ],
"st_diffs": [ "utf8_st_diffs": [
{ {
"diff": "Binary files a/.DS_Store and /dev/null differ\n", "diff": "Binary files a/.DS_Store and /dev/null differ\n",
"new_path": ".DS_Store", "new_path": ".DS_Store",
...@@ -3138,7 +3138,7 @@ ...@@ -3138,7 +3138,7 @@
"committer_email": "dmitriy.zaporozhets@gmail.com" "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", "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", "new_path": "files/ruby/feature.rb",
...@@ -3423,7 +3423,7 @@ ...@@ -3423,7 +3423,7 @@
"committer_email": "james@jameslopez.es" "committer_email": "james@jameslopez.es"
} }
], ],
"st_diffs": [ "utf8_st_diffs": [
{ {
"diff": "--- /dev/null\n+++ b/test\n", "diff": "--- /dev/null\n+++ b/test\n",
"new_path": "test", "new_path": "test",
...@@ -3960,7 +3960,7 @@ ...@@ -3960,7 +3960,7 @@
"committer_email": "dmitriy.zaporozhets@gmail.com" "committer_email": "dmitriy.zaporozhets@gmail.com"
} }
], ],
"st_diffs": [ "utf8_st_diffs": [
{ {
"diff": "Binary files a/.DS_Store and /dev/null differ\n", "diff": "Binary files a/.DS_Store and /dev/null differ\n",
"new_path": ".DS_Store", "new_path": ".DS_Store",
...@@ -4597,7 +4597,7 @@ ...@@ -4597,7 +4597,7 @@
"committer_email": "marmis85@gmail.com" "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", "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", "new_path": "CHANGELOG",
...@@ -5108,7 +5108,7 @@ ...@@ -5108,7 +5108,7 @@
"committer_email": "stanhu@packetzoom.com" "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", "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", "new_path": "CHANGELOG",
...@@ -5434,7 +5434,7 @@ ...@@ -5434,7 +5434,7 @@
"id": 11, "id": 11,
"state": "empty", "state": "empty",
"st_commits": null, "st_commits": null,
"st_diffs": [ "utf8_st_diffs": [
], ],
"merge_request_id": 11, "merge_request_id": 11,
...@@ -5961,7 +5961,7 @@ ...@@ -5961,7 +5961,7 @@
"committer_email": "dmitriy.zaporozhets@gmail.com" "committer_email": "dmitriy.zaporozhets@gmail.com"
} }
], ],
"st_diffs": [ "utf8_st_diffs": [
{ {
"diff": "Binary files a/.DS_Store and /dev/null differ\n", "diff": "Binary files a/.DS_Store and /dev/null differ\n",
"new_path": ".DS_Store", "new_path": ".DS_Store",
...@@ -6400,7 +6400,7 @@ ...@@ -6400,7 +6400,7 @@
"committer_email": "james@jameslopez.es" "committer_email": "james@jameslopez.es"
} }
], ],
"st_diffs": [ "utf8_st_diffs": [
{ {
"diff": "--- /dev/null\n+++ b/test\n", "diff": "--- /dev/null\n+++ b/test\n",
"new_path": "test", "new_path": "test",
......
...@@ -2,6 +2,7 @@ require 'spec_helper' ...@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
describe 'restore project tree' do describe 'restore project tree' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:namespace) { create(:namespace, owner: user) } let(:namespace) { create(:namespace, owner: user) }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
...@@ -53,6 +54,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do ...@@ -53,6 +54,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(event.note.noteable.project).not_to be_nil expect(event.note.noteable.project).not_to be_nil
end end
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 end
end end
...@@ -102,12 +102,17 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -102,12 +102,17 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
it 'has ci pipeline notes' do it 'has ci pipeline notes' do
expect(saved_project_json['pipelines'].first['notes']).not_to be_empty expect(saved_project_json['pipelines'].first['notes']).not_to be_empty
end 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
end end
def setup_project def setup_project
issue = create(:issue, assignee: user) issue = create(:issue, assignee: user)
merge_request = create(:merge_request)
label = create(:label) label = create(:label)
snippet = create(:project_snippet) snippet = create(:project_snippet)
release = create(:release) release = create(:release)
...@@ -115,12 +120,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -115,12 +120,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
project = create(:project, project = create(:project,
:public, :public,
issues: [issue], issues: [issue],
merge_requests: [merge_request],
labels: [label], labels: [label],
snippets: [snippet], snippets: [snippet],
releases: [release] releases: [release]
) )
merge_request = create(:merge_request, source_project: project)
commit_status = create(:commit_status, project: project) commit_status = create(:commit_status, project: project)
ci_pipeline = create(:ci_pipeline, ci_pipeline = create(:ci_pipeline,
......
...@@ -191,16 +191,16 @@ describe Ci::Build, models: true do ...@@ -191,16 +191,16 @@ describe Ci::Build, models: true do
end end
describe '#variables' do describe '#variables' do
context 'returns variables' do let(:predefined_variables) do
subject { build.variables } [
{ key: :CI_BUILD_NAME, value: 'test', public: true },
{ key: :CI_BUILD_STAGE, value: 'test', public: true },
]
end
let(:predefined_variables) do subject { build.variables }
[
{ key: :CI_BUILD_NAME, value: 'test', public: true },
{ key: :CI_BUILD_STAGE, value: 'stage', public: true },
]
end
context 'returns variables' do
let(:yaml_variables) do let(:yaml_variables) do
[ [
{ key: :DB_NAME, value: 'postgres', public: true } { key: :DB_NAME, value: 'postgres', public: true }
...@@ -208,7 +208,7 @@ describe Ci::Build, models: true do ...@@ -208,7 +208,7 @@ describe Ci::Build, models: true do
end end
before do before do
build.update_attributes(stage: 'stage', yaml_variables: yaml_variables) build.yaml_variables = yaml_variables
end end
it { is_expected.to eq(predefined_variables + yaml_variables) } it { is_expected.to eq(predefined_variables + yaml_variables) }
...@@ -262,6 +262,54 @@ describe Ci::Build, models: true do ...@@ -262,6 +262,54 @@ describe Ci::Build, models: true do
end end
end 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 end
describe '#has_tags?' do describe '#has_tags?' do
...@@ -670,4 +718,120 @@ describe Ci::Build, models: true do ...@@ -670,4 +718,120 @@ describe Ci::Build, models: true do
end end
end 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 end
...@@ -260,6 +260,68 @@ describe Ci::Pipeline, models: true do ...@@ -260,6 +260,68 @@ describe Ci::Pipeline, models: true do
expect(pipeline.reload.status).to eq('canceled') expect(pipeline.reload.status).to eq('canceled')
end end
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 end
context 'when no builds created' do context 'when no builds created' do
...@@ -416,4 +478,28 @@ describe Ci::Pipeline, models: true do ...@@ -416,4 +478,28 @@ describe Ci::Pipeline, models: true do
end end
end 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 end
...@@ -11,6 +11,7 @@ describe Deployment, models: true do ...@@ -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(:name).to(:environment).with_prefix }
it { is_expected.to delegate_method(:commit).to(:project) } 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(: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(:ref) }
it { is_expected.to validate_presence_of(:sha) } it { is_expected.to validate_presence_of(:sha) }
......
...@@ -211,4 +211,27 @@ describe API::Helpers, api: true do ...@@ -211,4 +211,27 @@ describe API::Helpers, api: true do
expect(sudo_identifier).to eq(' 123') expect(sudo_identifier).to eq(' 123')
end end
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 end
...@@ -32,6 +32,8 @@ describe API::API, api: true do ...@@ -32,6 +32,8 @@ describe API::API, api: true do
expect(json_response['name']).to eq(branch_name) expect(json_response['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha) expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(false) 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 end
it "should return a 403 error if guest" do it "should return a 403 error if guest" do
...@@ -45,14 +47,95 @@ describe API::API, api: true do ...@@ -45,14 +47,95 @@ describe API::API, api: true do
end end
end end
describe "PUT /projects/:id/repository/branches/:branch/protect" do describe 'PUT /projects/:id/repository/branches/:branch/protect' do
it "should protect a single branch" do it 'protects a single branch' do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
expect(response).to have_http_status(200) 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['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha) expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(true) 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 end
it "should return a 404 error if branch not found" do it "should return a 404 error if branch not found" do
......
...@@ -134,8 +134,7 @@ describe API::Todos, api: true do ...@@ -134,8 +134,7 @@ describe API::Todos, api: true do
delete api('/todos', john_doe) delete api('/todos', john_doe)
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response).to be_an Array expect(response.body).to eq('3')
expect(json_response.length).to eq(3)
expect(pending_1.reload).to be_done expect(pending_1.reload).to be_done
expect(pending_2.reload).to be_done expect(pending_2.reload).to be_done
expect(pending_3.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