Commit 8ead4b22 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin/master' into 15343-build-settiings

parents 4a74798a b9ed9d65
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) v 8.11.0 (unreleased)
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
- Limit git rev-list output count to one in forced push check
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)
- Expose {should,force}_remove_source_branch (Ben Boeckel) - Expose {should,force}_remove_source_branch (Ben Boeckel)
- Add the functionality to be able to rename a file. !5049 (tiagonbotelho)
- Disable PostgreSQL statement timeout during migrations - Disable PostgreSQL statement timeout during migrations
- Fix projects dropdown loading performance with a simplified api cal. !5113 (tiagonbotelho) - Fix projects dropdown loading performance with a simplified api cal. !5113 (tiagonbotelho)
- Fix commit builds API, return all builds for all pipelines for given commit. !4849 - Fix commit builds API, return all builds for all pipelines for given commit. !4849
...@@ -24,6 +26,7 @@ v 8.10.0 (unreleased) ...@@ -24,6 +26,7 @@ v 8.10.0 (unreleased)
- Escape file extension when parsing search results !5141 (winniehell) - Escape file extension when parsing search results !5141 (winniehell)
- Apply the trusted_proxies config to the rack request object for use with rack_attack - Apply the trusted_proxies config to the rack request object for use with rack_attack
- Upgrade to Rails 4.2.7. !5236 - Upgrade to Rails 4.2.7. !5236
- Allow to pull code with deploy key from public projects
- Add Sidekiq queue duration to transaction metrics. - Add Sidekiq queue duration to transaction metrics.
- Add a new column `artifacts_size` to table `ci_builds` !4964 - Add a new column `artifacts_size` to table `ci_builds` !4964
- Let Workhorse serve format-patch diffs - Let Workhorse serve format-patch diffs
...@@ -36,12 +39,15 @@ v 8.10.0 (unreleased) ...@@ -36,12 +39,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
...@@ -129,6 +135,7 @@ v 8.10.0 (unreleased) ...@@ -129,6 +135,7 @@ v 8.10.0 (unreleased)
- Fix MR diff encoding issues exporting GitLab projects - Fix MR diff encoding issues exporting GitLab projects
- Move builds settings out of project settings and rename Pipelines - Move builds settings out of project settings and rename Pipelines
- Add builds badge to Pipelines settings page - Add builds badge to Pipelines settings page
- Export and import avatar as part of project import/export
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
......
...@@ -578,7 +578,7 @@ GEM ...@@ -578,7 +578,7 @@ GEM
railties (>= 4.2.0, < 5.1) railties (>= 4.2.0, < 5.1)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (2.1.2)
rouge (2.0.3) rouge (2.0.5)
rqrcode (0.7.0) rqrcode (0.7.0)
chunky_png chunky_png
rqrcode-rails3 (0.1.7) rqrcode-rails3 (0.1.7)
......
...@@ -49,6 +49,17 @@ ...@@ -49,6 +49,17 @@
border-color: $border-dark; border-color: $border-dark;
color: $color; color: $color;
} }
svg {
path {
fill: $color;
}
use {
stroke: $color;
}
}
} }
@mixin btn-green { @mixin btn-green {
...@@ -173,6 +184,13 @@ ...@@ -173,6 +184,13 @@
.caret { .caret {
margin-left: 5px; margin-left: 5px;
} }
svg {
height: 15px;
width: auto;
position: relative;
top: 2px;
}
} }
.btn-lg { .btn-lg {
......
...@@ -198,6 +198,10 @@ header.header-pinned-nav { ...@@ -198,6 +198,10 @@ header.header-pinned-nav {
.sidebar-collapsed-icon { .sidebar-collapsed-icon {
cursor: pointer; cursor: pointer;
.btn {
background-color: $gray-light;
}
} }
} }
......
...@@ -122,7 +122,8 @@ ...@@ -122,7 +122,8 @@
button { button {
float: right; float: right;
padding: 3px 5px; padding: 1px 5px;
background-color: $gray-light;
} }
} }
......
...@@ -78,6 +78,14 @@ form.edit-issue { ...@@ -78,6 +78,14 @@ form.edit-issue {
} }
} }
.merge-request-ci-status {
svg {
margin-right: 4px;
position: relative;
top: 1px;
}
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.issue-btn-group { .issue-btn-group {
width: 100%; width: 100%;
......
...@@ -60,8 +60,10 @@ ...@@ -60,8 +60,10 @@
.ci_widget { .ci_widget {
border-bottom: 1px solid #eef0f2; border-bottom: 1px solid #eef0f2;
i { svg {
margin-right: 4px; margin-right: 4px;
position: relative;
top: 1px;
} }
&.ci-success { &.ci-success {
...@@ -196,6 +198,16 @@ ...@@ -196,6 +198,16 @@
.merge-request-title { .merge-request-title {
margin-bottom: 2px; margin-bottom: 2px;
.ci-status-link {
svg {
height: 16px;
width: 16px;
position: relative;
top: 3px;
}
}
} }
} }
......
.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 {
...@@ -45,6 +49,14 @@ ...@@ -45,6 +49,14 @@
.commit-link { .commit-link {
.ci-status {
svg {
top: 1px;
margin-right: 0;
}
}
a:hover { a:hover {
text-decoration: none; text-decoration: none;
} }
...@@ -53,9 +65,8 @@ ...@@ -53,9 +65,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 +75,15 @@ ...@@ -64,10 +75,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 +116,31 @@ ...@@ -100,6 +116,31 @@
} }
} }
.icon-container {
display: inline-block;
text-align: right;
width: 20px;
.fa {
position: relative;
right: 3px;
}
svg {
position: relative;
right: 1px;
}
}
.stage-cell {
svg {
height: 18px;
width: 18px;
vertical-align: middle;
}
}
.duration, .duration,
.finished-at { .finished-at {
color: $table-text-gray; color: $table-text-gray;
...@@ -107,21 +148,19 @@ ...@@ -107,21 +148,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;
......
...@@ -129,6 +129,17 @@ ...@@ -129,6 +129,17 @@
color: $layout-link-gray; color: $layout-link-gray;
} }
svg {
path {
fill: $layout-link-gray;
}
use {
stroke: $layout-link-gray;
}
}
.fa-caret-down { .fa-caret-down {
margin-left: 3px; margin-left: 3px;
} }
...@@ -486,6 +497,11 @@ pre.light-well { ...@@ -486,6 +497,11 @@ pre.light-well {
> span { > span {
margin-left: 10px; margin-left: 10px;
} }
svg {
position: relative;
top: 2px;
}
} }
} }
......
...@@ -41,6 +41,14 @@ ...@@ -41,6 +41,14 @@
color: $blue-normal; color: $blue-normal;
border-color: $blue-normal; border-color: $blue-normal;
} }
svg {
height: 13px;
width: 13px;
position: relative;
top: 1px;
margin: 0 3px;
}
} }
.ci-status-icon-success { .ci-status-icon-success {
......
.tag-buttons {
line-height: 40px;
.btn:not(.dropdown-toggle) {
margin-left: 10px;
}
}
...@@ -7,7 +7,8 @@ module CreatesCommit ...@@ -7,7 +7,8 @@ module CreatesCommit
commit_params = @commit_params.merge( commit_params = @commit_params.merge(
source_project: @project, source_project: @project,
source_branch: @ref, source_branch: @ref,
target_branch: @target_branch target_branch: @target_branch,
previous_path: @previous_path
) )
result = service.new(@tree_edit_project, current_user, commit_params).execute result = service.new(@tree_edit_project, current_user, commit_params).execute
......
...@@ -38,6 +38,12 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -38,6 +38,12 @@ class Projects::BlobController < Projects::ApplicationController
end end
def update def update
if params[:file_path].present?
@previous_path = @path
@path = params[:file_path]
@commit_params[:file_path] = @path
end
after_edit_path = after_edit_path =
if from_merge_request && @target_branch == @ref if from_merge_request && @target_branch == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
......
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)
......
...@@ -26,18 +26,20 @@ module CiStatusHelper ...@@ -26,18 +26,20 @@ module CiStatusHelper
icon_name = icon_name =
case status case status
when 'success' when 'success'
'check' 'icon_status_success'
when 'success_with_warnings'
'icon_status_warning'
when 'failed' when 'failed'
'close' 'icon_status_failed'
when 'pending' when 'pending'
'clock-o' 'icon_status_pending'
when 'running' when 'running'
'spinner' 'icon_status_running'
else else
'circle' 'icon_status_cancel'
end end
icon(icon_name + ' fw') custom_icon(icon_name)
end end
def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '') def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '')
......
module TimeHelper module TimeHelper
def duration_in_words(finished_at, started_at)
if finished_at && started_at
interval_in_seconds = finished_at.to_i - started_at.to_i
elsif started_at
interval_in_seconds = Time.now.to_i - started_at.to_i
end
time_interval_in_words(interval_in_seconds)
end
def time_interval_in_words(interval_in_seconds) def time_interval_in_words(interval_in_seconds)
minutes = interval_in_seconds / 60 minutes = interval_in_seconds / 60
seconds = interval_in_seconds - minutes * 60 seconds = interval_in_seconds - minutes * 60
...@@ -25,9 +15,19 @@ module TimeHelper ...@@ -25,9 +15,19 @@ module TimeHelper
end end
def duration_in_numbers(finished_at, started_at) def duration_in_numbers(finished_at, started_at)
diff_in_seconds = finished_at.to_i - started_at.to_i interval = interval_in_seconds(started_at, finished_at)
time_format = diff_in_seconds < 1.hour ? "%M:%S" : "%H:%M:%S" time_format = interval < 1.hour ? "%M:%S" : "%H:%M:%S"
Time.at(diff_in_seconds).utc.strftime(time_format) Time.at(interval).utc.strftime(time_format)
end
private
def interval_in_seconds(started_at, finished_at = nil)
if started_at && finished_at
finished_at.to_i - started_at.to_i
elsif started_at
Time.now.to_i - started_at.to_i
end
end end
end end
...@@ -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
...@@ -385,6 +404,14 @@ module Ci ...@@ -385,6 +404,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
...@@ -427,5 +454,11 @@ module Ci ...@@ -427,5 +454,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
...@@ -733,6 +733,33 @@ class Repository ...@@ -733,6 +733,33 @@ class Repository
end end
end end
def update_file(user, path, content, branch:, previous_path:, message:)
commit_with_hooks(user, branch) do |ref|
committer = user_to_committer(user)
options = {}
options[:committer] = committer
options[:author] = committer
options[:commit] = {
message: message,
branch: ref,
update_ref: false
}
options[:file] = {
content: content,
path: path,
update: true
}
if previous_path
options[:file][:previous_path] = previous_path
Gitlab::Git::Blob.rename(raw_repository, options)
else
Gitlab::Git::Blob.commit(raw_repository, options)
end
end
end
def remove_file(user, path, message, branch) def remove_file(user, path, message, branch)
commit_with_hooks(user, branch) do |ref| commit_with_hooks(user, branch) do |ref|
committer = user_to_committer(user) committer = user_to_committer(user)
......
...@@ -854,7 +854,7 @@ class User < ActiveRecord::Base ...@@ -854,7 +854,7 @@ class User < ActiveRecord::Base
groups.joins(:shared_projects).select(:project_id)] groups.joins(:shared_projects).select(:project_id)]
if min_access_level if min_access_level
scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } } scope = { access_level: Gitlab::Access.all_values.select { |access| access >= min_access_level } }
relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) } relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) }
end end
......
...@@ -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.
......
...@@ -9,12 +9,14 @@ module Files ...@@ -9,12 +9,14 @@ module Files
@commit_message = params[:commit_message] @commit_message = params[:commit_message]
@file_path = params[:file_path] @file_path = params[:file_path]
@previous_path = params[:previous_path]
@file_content = if params[:file_content_encoding] == 'base64' @file_content = if params[:file_content_encoding] == 'base64'
Base64.decode64(params[:file_content]) Base64.decode64(params[:file_content])
else else
params[:file_content] params[:file_content]
end end
# Validate parameters
validate validate
# Create new branch if it different from source_branch # Create new branch if it different from source_branch
......
...@@ -3,7 +3,10 @@ require_relative "base_service" ...@@ -3,7 +3,10 @@ require_relative "base_service"
module Files module Files
class UpdateService < Files::BaseService class UpdateService < Files::BaseService
def commit def commit
repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, true) repository.update_file(current_user, @file_path, @file_content,
branch: @target_branch,
previous_path: @previous_path,
message: @commit_message)
end end
end end
end end
...@@ -9,7 +9,7 @@ module Projects ...@@ -9,7 +9,7 @@ module Projects
private private
def save_all def save_all
if [version_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save) if [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save)
Gitlab::ImportExport::Saver.save(project: project, shared: @shared) Gitlab::ImportExport::Saver.save(project: project, shared: @shared)
notify_success notify_success
else else
...@@ -21,6 +21,10 @@ module Projects ...@@ -21,6 +21,10 @@ module Projects
Gitlab::ImportExport::VersionSaver.new(shared: @shared) Gitlab::ImportExport::VersionSaver.new(shared: @shared)
end end
def avatar_saver
Gitlab::ImportExport::AvatarSaver.new(project: project, shared: @shared)
end
def project_tree_saver def project_tree_saver
Gitlab::ImportExport::ProjectTreeSaver.new(project: project, shared: @shared) Gitlab::ImportExport::ProjectTreeSaver.new(project: project, shared: @shared)
end end
......
...@@ -14,4 +14,8 @@ class AvatarUploader < CarrierWave::Uploader::Base ...@@ -14,4 +14,8 @@ class AvatarUploader < CarrierWave::Uploader::Base
def reset_events_cache(file) def reset_events_cache(file)
model.reset_events_cache if model.is_a?(User) model.reset_events_cache if model.is_a?(User)
end end
def exists?
model.avatar.file && model.avatar.file.exists?
end
end end
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
= icon('code-fork') = icon('code-fork')
= ref = ref
%span.editor-file-name %span.editor-file-name
= @path - if current_action?(:edit) || current_action?(:update)
= text_field_tag 'file_path', (params[:file_path] || @path),
class: 'form-control new-file-path'
- if current_action?(:new) || current_action?(:create) - if current_action?(:new) || current_action?(:create)
%span.editor-file-name %span.editor-file-name
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
- if @build.duration - if @build.duration
%p.build-detail-row %p.build-detail-row
%span.build-light-text Duration: %span.build-light-text Duration:
#{duration_in_words(@build.finished_at, @build.started_at)} = time_interval_in_words(@build.duration)
- if @build.finished_at - if @build.finished_at
%p.build-detail-row %p.build-detail-row
%span.build-light-text Finished: %span.build-light-text Finished:
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- if current_user && can?(current_user, :fork_project, @project) - if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do
= icon('code-fork fw') = custom_icon('icon_fork')
Fork Fork
%div.count-with-arrow %div.count-with-arrow
%span.arrow %span.arrow
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
= @project.forks_count = @project.forks_count
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
= icon('code-fork fw') = custom_icon('icon_fork')
Fork Fork
%div.count-with-arrow %div.count-with-arrow
%span.arrow %span.arrow
......
...@@ -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
- 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 = 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') = 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"
.icon-container
= custom_icon("icon_commit") = 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
...@@ -34,7 +35,7 @@ ...@@ -34,7 +35,7 @@
- stages_status = pipeline.statuses.latest.stages_status - stages_status = pipeline.statuses.latest.stages_status
- stages.each do |stage| - stages.each do |stage|
%td %td.stage-cell
- status = stages_status[stage] - status = stages_status[stage]
- tooltip = "#{stage.titleize}: #{status || 'not found'}" - tooltip = "#{stage.titleize}: #{status || 'not found'}"
- if status - if status
...@@ -57,8 +58,21 @@ ...@@ -57,8 +58,21 @@
%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? }
- 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? - if artifacts.present?
.inline
.btn-group .btn-group
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'} %a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
= icon("download") = icon("download")
......
- 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
......
...@@ -31,11 +31,11 @@ ...@@ -31,11 +31,11 @@
- if current_user && can?(current_user, :fork_project, @project) - if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
= icon('code-fork fw') = custom_icon('icon_fork')
Fork Fork
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
= icon('code-fork fw') = custom_icon('icon_fork')
Fork Fork
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
%td.duration %td.duration
- if generic_commit_status.duration - if generic_commit_status.duration
= icon("clock-o") = icon("clock-o")
#{duration_in_words(generic_commit_status.finished_at, generic_commit_status.started_at)} = time_interval_in_words(generic_commit_status.duration)
%td.timestamp %td.timestamp
- if generic_commit_status.finished_at - if generic_commit_status.finished_at
......
...@@ -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
......
- @no_container = true
- page_title @tag.name, "Tags" - page_title @tag.name, "Tags"
= render "projects/commits/head" = render "projects/commits/head"
.row-content-block %div{ class: container_class }
.pull-right .sub-header-block
.pull-right.tag-buttons
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has-tooltip', title: 'Edit release notes' do = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Edit release notes' do
= icon("pencil") = icon("pencil")
= link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse files' do = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse files' do
= icon('files-o') = icon('files-o')
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse commits' do = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
= icon('history') = icon('history')
- if can? current_user, :download_code, @project - if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: @tag.name, project: @project = render 'projects/tags/download', ref: @tag.name, project: @project
...@@ -16,6 +18,7 @@ ...@@ -16,6 +18,7 @@
.pull-right .pull-right
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o %i.fa.fa-trash-o
.tag-info.append-bottom-10
.title .title
%span.item-title= @tag.name %span.item-title= @tag.name
- if @commit - if @commit
...@@ -26,7 +29,7 @@ ...@@ -26,7 +29,7 @@
%pre.body %pre.body
= strip_gpg_signature(@tag.message) = strip_gpg_signature(@tag.message)
.append-bottom-default.prepend-top-default .append-bottom-default.prepend-top-default
- if @release.description.present? - if @release.description.present?
.description .description
.wiki .wiki
......
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle id="a" cx="4" cy="4" r="4"/><mask id="d" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><circle id="b" cx="20" cy="4" r="4"/><mask id="e" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><circle id="c" cx="12" cy="30" r="4"/><mask id="f" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(8 3)"><path fill="#7E7E7E" d="M10 19.667c-4.14-1.29-7.389-5.878-7.389-5.878C2.274 13.353 2 12.545 2 12.01V6h4v5.509c0 .276.166.65.367.831 0 0 1.136 1.028 1.746 1.574C9.617 15.261 11.048 16 12.09 16c1.028 0 2.41-.723 3.858-2.048.588-.54 1.84-1.742 1.84-1.742a.784.784 0 0 0 .211-.502V6h4v6.008c0 .548-.259 1.349-.601 1.795 0 0-3.21 4.707-7.399 5.916V27h-4v-7.333z"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#d)" xlink:href="#a"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#e)" xlink:href="#b"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#f)" xlink:href="#c"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="a" cx="7" cy="7" r="7"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<use stroke="#5C5C5C" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
<rect width="10" height="1" x="2" y="6.5" fill="#5C5C5C" transform="rotate(45 7 7)" rx=".3"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="a" cx="7" cy="7" r="7"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<use stroke="#D22852" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
<path fill="#D22852" d="M7.5,6.5 L7.5,4.30578971 C7.5,4.12531853 7.36809219,4 7.20537567,4 L6.79462433,4 C6.63904572,4 6.5,4.13690672 6.5,4.30578971 L6.5,6.5 L4.30578971,6.5 C4.12531853,6.5 4,6.63190781 4,6.79462433 L4,7.20537567 C4,7.36095428 4.13690672,7.5 4.30578971,7.5 L6.5,7.5 L6.5,9.69421029 C6.5,9.87468147 6.63190781,10 6.79462433,10 L7.20537567,10 C7.36095428,10 7.5,9.86309328 7.5,9.69421029 L7.5,7.5 L9.69421029,7.5 C9.87468147,7.5 10,7.36809219 10,7.20537567 L10,6.79462433 C10,6.63904572 9.86309328,6.5 9.69421029,6.5 L7.5,6.5 Z" transform="rotate(45 7 7)"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="a" cx="7" cy="7" r="7"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<use stroke="#E75E40" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
<rect width="1" height="4" x="5" y="5" fill="#E75E40" rx=".3"/>
<rect width="1" height="4" x="8" y="5" fill="#E75E40" rx=".3"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="a" cx="7" cy="7" r="7"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<use stroke="#2D9FD8" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
<path fill="#2D9FD8" d="M7,3.00800862 C9.09023405,3.13960661 10.7448145,4.87657932 10.7448145,7 C10.7448145,9.209139 8.95395346,11 6.74481446,11 C5.4560962,11 4.30972054,10.3905589 3.57817301,9.44416214 L7,7 L7,3.00800862 Z"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="a" cx="7" cy="7" r="7"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<use stroke="#31AF64" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
<g fill="#31AF64" transform="rotate(45 -.13 10.953)">
<rect width="1" height="5" x="2" rx=".3"/>
<rect width="3" height="1" y="4" rx=".3"/>
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<circle id="a" cx="7" cy="7" r="7"/>
<mask id="b" width="14" height="14" x="0" y="0" fill="white">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<g fill="#FF8A24" transform="translate(6 3)">
<rect width="2" height="5" rx=".5"/>
<rect width="2" height="2" y="6" rx=".5"/>
</g>
<use stroke="#FF8A24" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
</g>
</svg>
# 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
...@@ -89,11 +89,10 @@ Rails.application.routes.draw do ...@@ -89,11 +89,10 @@ Rails.application.routes.draw do
mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/.match(request.path_info) }, via: [:get, :post, :put] mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/.match(request.path_info) }, via: [:get, :post, :put]
# Help # Help
get 'help' => 'help#index' get 'help' => 'help#index'
get 'help/*path' => 'help#show', as: :help_page get 'help/shortcuts' => 'help#shortcuts'
get 'help/shortcuts'
get 'help/ui' => 'help#ui' get 'help/ui' => 'help#ui'
get 'help/*path' => 'help#show', as: :help_page
# #
# Global snippets # Global snippets
...@@ -755,6 +754,7 @@ Rails.application.routes.draw do ...@@ -755,6 +754,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', nil, 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,6 +61,7 @@ class Gitlab::Seeder::Builds ...@@ -40,6 +61,7 @@ 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)
...@@ -47,12 +69,20 @@ class Gitlab::Seeder::Builds ...@@ -47,12 +69,20 @@ class Gitlab::Seeder::Builds
end end
end end
def build_attributes_for(ci_commit) def commit_status_create!(pipeline, opts = {})
{ name: 'test build', commands: "$ build command", attributes = commit_status_attributes_for(pipeline, opts)
stage: 'test', stage_idx: 1, ref: 'master', GenericCommitStatus.create(attributes)
user_id: build_user, gl_project_id: @project.id, end
status: build_status, commit_id: ci_commit.id,
created_at: Time.now, updated_at: Time.now } 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(pipeline, opts)
commit_status_attributes_for(pipeline, opts).merge(commands: '$ build command')
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)
......
...@@ -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
......
...@@ -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
...@@ -60,7 +62,7 @@ module API ...@@ -60,7 +62,7 @@ module API
developers_can_merge: developers_can_merge || false) developers_can_merge: developers_can_merge || false)
end 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
...@@ -79,7 +81,7 @@ module API ...@@ -79,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
...@@ -97,7 +99,7 @@ module API ...@@ -97,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)
......
...@@ -114,33 +114,23 @@ module API ...@@ -114,33 +114,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_obj, options| expose :protected do |repo_branch, options|
if options[:project] options[:project].protected_branch? repo_branch.name
options[:project].protected_branch? repo_obj.name
end
end end
expose :developers_can_push do |repo_obj, options| expose :developers_can_push do |repo_branch, options|
if options[:project] options[:project].developers_can_push_to_protected_branch? repo_branch.name
options[:project].developers_can_push_to_protected_branch? repo_obj.name
end
end end
expose :developers_can_merge do |repo_obj, options| expose :developers_can_merge do |repo_branch, options|
if options[:project] options[:project].developers_can_merge_to_protected_branch? repo_branch.name
options[:project].developers_can_merge_to_protected_branch? repo_obj.name
end
end end
end end
...@@ -437,27 +427,14 @@ module API ...@@ -437,27 +427,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
......
...@@ -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])
......
...@@ -8,8 +8,8 @@ module Gitlab ...@@ -8,8 +8,8 @@ module Gitlab
if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev) if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
false false
else else
missed_refs, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})) missed_ref, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list --max-count=1 #{oldrev} ^#{newrev}))
missed_refs.split("\n").size > 0 missed_ref.present?
end end
end end
end end
......
...@@ -110,6 +110,7 @@ module Gitlab ...@@ -110,6 +110,7 @@ module Gitlab
def deploy_key_can_read_project? def deploy_key_can_read_project?
if deploy_key if deploy_key
return true if project.public?
deploy_key.projects.include?(project) deploy_key.projects.include?(project)
else else
false false
......
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.max_file_size = current_application_settings.max_attachment_size gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = help_shortcuts_path gon.shortcuts_path = help_page_path('shortcuts')
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
gon.award_menu_url = emojis_path gon.award_menu_url = emojis_path
......
module Gitlab
module ImportExport
class AvatarRestorer
def initialize(project:, shared:)
@project = project
@shared = shared
end
def restore
return true unless avatar_export_file
@project.avatar = File.open(avatar_export_file)
@project.save!
rescue => e
@shared.error(e)
false
end
private
def avatar_export_file
@avatar_export_file ||= Dir["#{avatar_export_path}/*"].first
end
def avatar_export_path
File.join(@shared.export_path, 'avatar')
end
end
end
end
module Gitlab
module ImportExport
class AvatarSaver
include Gitlab::ImportExport::CommandLineUtil
def initialize(project:, shared:)
@project = project
@shared = shared
end
def save
return true unless @project.avatar.exists?
copy_files(avatar_path, avatar_export_path)
rescue => e
@shared.error(e)
false
end
private
def avatar_export_path
File.join(@shared.export_path, 'avatar', @project.avatar_identifier)
end
def avatar_path
@project.avatar.path
end
end
end
end
...@@ -36,6 +36,15 @@ module Gitlab ...@@ -36,6 +36,15 @@ module Gitlab
def git_bin_path def git_bin_path
Gitlab.config.git.bin_path Gitlab.config.git.bin_path
end end
def copy_files(source, destination)
# if we are copying files, create the destination folder
destination_folder = File.file?(source) ? File.dirname(destination) : destination
FileUtils.mkdir_p(destination_folder)
FileUtils.copy_entry(source, destination)
true
end
end end
end end
end end
...@@ -9,7 +9,7 @@ module Gitlab ...@@ -9,7 +9,7 @@ module Gitlab
end end
def execute def execute
if import_file && check_version! && [project_tree, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore) if import_file && check_version! && [project_tree, avatar_restorer, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore)
project_tree.restored_project project_tree.restored_project
else else
raise Projects::ImportService::Error.new(@shared.errors.join(', ')) raise Projects::ImportService::Error.new(@shared.errors.join(', '))
...@@ -35,6 +35,10 @@ module Gitlab ...@@ -35,6 +35,10 @@ module Gitlab
project: @project) project: @project)
end end
def avatar_restorer
Gitlab::ImportExport::AvatarRestorer.new(project: project_tree.restored_project, shared: @shared)
end
def repo_restorer def repo_restorer
Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: repo_path, Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: repo_path,
shared: @shared, shared: @shared,
......
module Gitlab module Gitlab
module ImportExport module ImportExport
class UploadsSaver class UploadsSaver
include Gitlab::ImportExport::CommandLineUtil
def initialize(project:, shared:) def initialize(project:, shared:)
@project = project @project = project
@shared = shared @shared = shared
...@@ -17,12 +19,6 @@ module Gitlab ...@@ -17,12 +19,6 @@ module Gitlab
private private
def copy_files(source, destination)
FileUtils.mkdir_p(destination)
FileUtils.copy_entry(source, destination)
true
end
def uploads_export_path def uploads_export_path
File.join(@shared.export_path, 'uploads') File.join(@shared.export_path, 'uploads')
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
...@@ -63,4 +63,13 @@ describe HelpController do ...@@ -63,4 +63,13 @@ describe HelpController do
end end
end end
end end
describe 'GET #ui' do
context 'for UI Development Kit' do
it 'renders found' do
get :ui
expect(response).to have_http_status(200)
end
end
end
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
......
...@@ -63,6 +63,20 @@ feature "Pipelines", feature: true do ...@@ -63,6 +63,20 @@ feature "Pipelines", feature: true 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') }
...@@ -118,6 +132,7 @@ feature "Pipelines", feature: true do ...@@ -118,6 +132,7 @@ feature "Pipelines", feature: true 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
...@@ -132,6 +147,7 @@ feature "Pipelines", feature: true do ...@@ -132,6 +147,7 @@ feature "Pipelines", feature: true 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
...@@ -155,6 +171,12 @@ feature "Pipelines", feature: true do ...@@ -155,6 +171,12 @@ feature "Pipelines", feature: true 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
......
...@@ -7,7 +7,13 @@ describe CiStatusHelper do ...@@ -7,7 +7,13 @@ describe CiStatusHelper do
let(:failed_commit) { double("Ci::Pipeline", status: 'failed') } let(:failed_commit) { double("Ci::Pipeline", status: 'failed') }
describe 'ci_icon_for_status' do describe 'ci_icon_for_status' do
it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') } it 'renders to correct svg on success' do
it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') } expect(helper).to receive(:render).with('shared/icons/icon_status_success.svg', anything)
helper.ci_icon_for_status(success_commit.status)
end
it 'renders the correct svg on failure' do
expect(helper).to receive(:render).with('shared/icons/icon_status_failed.svg', anything)
helper.ci_icon_for_status(failed_commit.status)
end
end end
end end
require 'spec_helper' require 'spec_helper'
describe TimeHelper do describe TimeHelper do
describe "#duration_in_words" do describe "#time_interval_in_words" do
it "returns minutes and seconds" do it "returns minutes and seconds" do
intervals_in_words = { intervals_in_words = {
100 => "1 minute 40 seconds", 100 => "1 minute 40 seconds",
...@@ -11,26 +11,23 @@ describe TimeHelper do ...@@ -11,26 +11,23 @@ describe TimeHelper do
} }
intervals_in_words.each do |interval, expectation| intervals_in_words.each do |interval, expectation|
expect(duration_in_words(Time.now + interval, Time.now)).to eq(expectation) expect(time_interval_in_words(interval)).to eq(expectation)
end
end end
it "calculates interval from now if there is no finished_at" do
expect(duration_in_words(nil, Time.now - 5)).to eq("5 seconds")
end end
end end
describe "#time_interval_in_words" do describe "#duration_in_numbers" do
it "returns minutes and seconds" do it "returns minutes and seconds" do
intervals_in_words = { duration_in_numbers = {
100 => "1 minute 40 seconds", [100, 0] => "01:40",
121 => "2 minutes 1 second", [121, 0] => "02:01",
3721 => "62 minutes 1 second", [3721, 0] => "01:02:01",
0 => "0 seconds" [0, 0] => "00:00",
[nil, Time.now.to_i - 42] => "00:42"
} }
intervals_in_words.each do |interval, expectation| duration_in_numbers.each do |interval, expectation|
expect(time_interval_in_words(interval)).to eq(expectation) expect(duration_in_numbers(*interval)).to eq(expectation)
end end
end end
end end
......
...@@ -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)
......
...@@ -44,12 +44,12 @@ describe Gitlab::GitAccess, lib: true do ...@@ -44,12 +44,12 @@ describe Gitlab::GitAccess, lib: true do
end end
describe 'download_access_check' do describe 'download_access_check' do
subject { access.check('git-upload-pack') }
describe 'master permissions' do describe 'master permissions' do
before { project.team << [user, :master] } before { project.team << [user, :master] }
context 'pull code' do context 'pull code' do
subject { access.download_access_check }
it { expect(subject.allowed?).to be_truthy } it { expect(subject.allowed?).to be_truthy }
end end
end end
...@@ -58,8 +58,6 @@ describe Gitlab::GitAccess, lib: true do ...@@ -58,8 +58,6 @@ describe Gitlab::GitAccess, lib: true do
before { project.team << [user, :guest] } before { project.team << [user, :guest] }
context 'pull code' do context 'pull code' do
subject { access.download_access_check }
it { expect(subject.allowed?).to be_falsey } it { expect(subject.allowed?).to be_falsey }
end end
end end
...@@ -71,16 +69,12 @@ describe Gitlab::GitAccess, lib: true do ...@@ -71,16 +69,12 @@ describe Gitlab::GitAccess, lib: true do
end end
context 'pull code' do context 'pull code' do
subject { access.download_access_check }
it { expect(subject.allowed?).to be_falsey } it { expect(subject.allowed?).to be_falsey }
end end
end end
describe 'without acccess to project' do describe 'without acccess to project' do
context 'pull code' do context 'pull code' do
subject { access.download_access_check }
it { expect(subject.allowed?).to be_falsey } it { expect(subject.allowed?).to be_falsey }
end end
end end
...@@ -90,10 +84,31 @@ describe Gitlab::GitAccess, lib: true do ...@@ -90,10 +84,31 @@ describe Gitlab::GitAccess, lib: true do
let(:actor) { key } let(:actor) { key }
context 'pull code' do context 'pull code' do
context 'when project is authorized' do
before { key.projects << project } before { key.projects << project }
subject { access.download_access_check }
it { expect(subject.allowed?).to be_truthy } it { expect(subject).to be_allowed }
end
context 'when unauthorized' do
context 'from public project' do
let(:project) { create(:project, :public) }
it { expect(subject).to be_allowed }
end
context 'from internal project' do
let(:project) { create(:project, :internal) }
it { expect(subject).not_to be_allowed }
end
context 'from private project' do
let(:project) { create(:project, :internal) }
it { expect(subject).not_to be_allowed }
end
end
end end
end end
end end
...@@ -240,5 +255,40 @@ describe Gitlab::GitAccess, lib: true do ...@@ -240,5 +255,40 @@ describe Gitlab::GitAccess, lib: true do
run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true })) run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true }))
end end
end end
describe 'deploy key permissions' do
let(:key) { create(:deploy_key) }
let(:actor) { key }
context 'push code' do
subject { access.check('git-receive-pack') }
context 'when project is authorized' do
before { key.projects << project }
it { expect(subject).not_to be_allowed }
end
context 'when unauthorized' do
context 'to public project' do
let(:project) { create(:project, :public) }
it { expect(subject).not_to be_allowed }
end
context 'to internal project' do
let(:project) { create(:project, :internal) }
it { expect(subject).not_to be_allowed }
end
context 'to private project' do
let(:project) { create(:project, :internal) }
it { expect(subject).not_to be_allowed }
end
end
end
end
end end
end end
require 'spec_helper'
describe Gitlab::ImportExport::AvatarRestorer, lib: true do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
let(:project) { create(:empty_project) }
before do
allow_any_instance_of(described_class).to receive(:avatar_export_file)
.and_return(Rails.root + "spec/fixtures/dk.png")
end
after do
project.remove_avatar!
end
it 'restores a project avatar' do
expect(described_class.new(project: project, shared: shared).restore).to be true
end
it 'saves the avatar into the project' do
described_class.new(project: project, shared: shared).restore
expect(project.reload.avatar.file.exists?).to be true
end
end
require 'spec_helper'
describe Gitlab::ImportExport::AvatarSaver, lib: true do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" }
let(:project_with_avatar) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
let(:project) { create(:empty_project) }
before do
FileUtils.mkdir_p("#{shared.export_path}/avatar/")
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end
after do
FileUtils.rm_rf("#{shared.export_path}/avatar")
end
it 'saves a project avatar' do
described_class.new(project: project_with_avatar, shared: shared).save
expect(File).to exist("#{shared.export_path}/avatar/dk.png")
end
it 'is fine not to have an avatar' do
expect(described_class.new(project: project, shared: shared).save).to be true
end
end
...@@ -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
subject { build.variables }
let(:predefined_variables) do let(:predefined_variables) do
[ [
{ key: :CI_BUILD_NAME, value: 'test', public: true }, { key: :CI_BUILD_NAME, value: 'test', public: true },
{ key: :CI_BUILD_STAGE, value: 'stage', public: true }, { key: :CI_BUILD_STAGE, value: 'test', public: true },
] ]
end end
subject { build.variables }
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) }
......
...@@ -130,6 +130,36 @@ describe Repository, models: true do ...@@ -130,6 +130,36 @@ describe Repository, models: true do
end end
end end
describe :commit_file do
it 'commits change to a file successfully' do
expect do
repository.commit_file(user, 'CHANGELOG', 'Changelog!',
'Updates file content',
'master', true)
end.to change { repository.commits('master').count }.by(1)
blob = repository.blob_at('master', 'CHANGELOG')
expect(blob.data).to eq('Changelog!')
end
end
describe :update_file do
it 'updates filename successfully' do
expect do
repository.update_file(user, 'NEWLICENSE', 'Copyright!',
branch: 'master',
previous_path: 'LICENSE',
message: 'Changes filename')
end.to change { repository.commits('master').count }.by(1)
files = repository.ls_files('master')
expect(files).not_to include('LICENSE')
expect(files).to include('NEWLICENSE')
end
end
describe "search_files" do describe "search_files" do
let(:results) { repository.search_files('feature', 'master') } let(:results) { repository.search_files('feature', 'master') }
subject { results } subject { results }
......
...@@ -887,16 +887,25 @@ describe User, models: true do ...@@ -887,16 +887,25 @@ describe User, models: true do
end end
describe '#authorized_projects' do describe '#authorized_projects' do
let!(:user) { create(:user) } context 'with a minimum access level' do
let!(:private_project) { create(:project, :private) } it 'includes projects for which the user is an owner' do
user = create(:user)
project = create(:empty_project, :private, namespace: user.namespace)
before do expect(user.authorized_projects(Gitlab::Access::REPORTER))
private_project.team << [user, Gitlab::Access::MASTER] .to contain_exactly(project)
end end
subject { user.authorized_projects } it 'includes projects for which the user is a master' do
user = create(:user)
project = create(:empty_project, :private)
it { is_expected.to eq([private_project]) } project.team << [user, Gitlab::Access::MASTER]
expect(user.authorized_projects(Gitlab::Access::REPORTER))
.to contain_exactly(project)
end
end
end end
describe '#ci_authorized_runners' do describe '#ci_authorized_runners' do
......
...@@ -116,12 +116,9 @@ describe HelpController, "routing" do ...@@ -116,12 +116,9 @@ describe HelpController, "routing" do
expect(get(path)).to route_to('help#show', expect(get(path)).to route_to('help#show',
path: 'workflow/protected_branches/protected_branches1', path: 'workflow/protected_branches/protected_branches1',
format: 'png') format: 'png')
path = '/help/shortcuts'
expect(get(path)).to route_to('help#show',
path: 'shortcuts')
path = '/help/ui' path = '/help/ui'
expect(get(path)).to route_to('help#show', expect(get(path)).to route_to('help#ui')
path: 'ui')
end end
end end
......
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