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.
v 8.11.0 (unreleased)
- 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)
- Fix profile activity heatmap to show correct day name (eanplatter)
- 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
- 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
......@@ -24,6 +26,7 @@ v 8.10.0 (unreleased)
- Escape file extension when parsing search results !5141 (winniehell)
- Apply the trusted_proxies config to the rack request object for use with rack_attack
- 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 a new column `artifacts_size` to table `ci_builds` !4964
- Let Workhorse serve format-patch diffs
......@@ -36,12 +39,15 @@ v 8.10.0 (unreleased)
- Support U2F devices in Firefox. !5177
- Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
- Add Spring EmojiOne updates.
- Added Rake task for tracking deployments !5320
- Fix fetching LFS objects for private CI projects
- Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237
- Add syntax for multiline blockquote using `>>>` fence !3954
- Fix viewing notification settings when a project is pending deletion
- Updated compare dropdown menus to use GL dropdown
- Redirects back to issue after clicking login link
- Eager load award emoji on notes
- Allow to define manual actions/builds on Pipelines and Environments
- Fix pagination when sorting by columns with lots of ties (like priority)
- The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020
- Updated project header design
......@@ -129,6 +135,7 @@ v 8.10.0 (unreleased)
- Fix MR diff encoding issues exporting GitLab projects
- Move builds settings out of project settings and rename Pipelines
- Add builds badge to Pipelines settings page
- Export and import avatar as part of project import/export
v 8.9.6
- Fix importing of events under notes for GitLab projects. !5154
......
......@@ -578,7 +578,7 @@ GEM
railties (>= 4.2.0, < 5.1)
rinku (2.0.0)
rotp (2.1.2)
rouge (2.0.3)
rouge (2.0.5)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
......
......@@ -49,6 +49,17 @@
border-color: $border-dark;
color: $color;
}
svg {
path {
fill: $color;
}
use {
stroke: $color;
}
}
}
@mixin btn-green {
......@@ -173,6 +184,13 @@
.caret {
margin-left: 5px;
}
svg {
height: 15px;
width: auto;
position: relative;
top: 2px;
}
}
.btn-lg {
......
......@@ -198,6 +198,10 @@ header.header-pinned-nav {
.sidebar-collapsed-icon {
cursor: pointer;
.btn {
background-color: $gray-light;
}
}
}
......
......@@ -122,7 +122,8 @@
button {
float: right;
padding: 3px 5px;
padding: 1px 5px;
background-color: $gray-light;
}
}
......
......@@ -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) {
.issue-btn-group {
width: 100%;
......
......@@ -60,8 +60,10 @@
.ci_widget {
border-bottom: 1px solid #eef0f2;
i {
svg {
margin-right: 4px;
position: relative;
top: 1px;
}
&.ci-success {
......@@ -196,6 +198,16 @@
.merge-request-title {
margin-bottom: 2px;
.ci-status-link {
svg {
height: 16px;
width: 16px;
position: relative;
top: 3px;
}
}
}
}
......
.pipelines {
.stage {
max-width: 80px;
width: 80px;
max-width: 90px;
width: 90px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
......@@ -30,13 +30,17 @@
}
.table.builds {
min-width: 1100px;
min-width: 1200px;
tr {
th {
padding: 16px;
padding: 16px 8px;
border: none;
}
td {
padding: 10px 8px;
}
}
tbody {
......@@ -45,6 +49,14 @@
.commit-link {
.ci-status {
svg {
top: 1px;
margin-right: 0;
}
}
a:hover {
text-decoration: none;
}
......@@ -53,9 +65,8 @@
.branch-commit {
.branch-name {
margin-left: 8px;
font-weight: bold;
max-width: 180px;
max-width: 150px;
overflow: hidden;
display: inline-block;
white-space: nowrap;
......@@ -64,10 +75,15 @@
}
svg {
margin: 0 6px;
height: 14px;
width: auto;
vertical-align: middle;
fill: $table-text-gray;
}
.fa {
font-size: 12px;
color: $table-text-gray;
}
.commit-id {
......@@ -100,6 +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,
.finished-at {
color: $table-text-gray;
......@@ -107,21 +148,19 @@
.fa {
font-size: 12px;
margin-right: 4px;
}
svg {
height: 12px;
width: auto;
width: 12px;
height: auto;
vertical-align: middle;
}
.fa,
svg {
margin-right: 5px;
margin-right: 4px;
}
}
.pipeline-actions {
min-width: 140px;
.btn {
margin: 0;
......
......@@ -129,6 +129,17 @@
color: $layout-link-gray;
}
svg {
path {
fill: $layout-link-gray;
}
use {
stroke: $layout-link-gray;
}
}
.fa-caret-down {
margin-left: 3px;
}
......@@ -486,6 +497,11 @@ pre.light-well {
> span {
margin-left: 10px;
}
svg {
position: relative;
top: 2px;
}
}
}
......
......@@ -41,6 +41,14 @@
color: $blue-normal;
border-color: $blue-normal;
}
svg {
height: 13px;
width: 13px;
position: relative;
top: 1px;
margin: 0 3px;
}
}
.ci-status-icon-success {
......
.tag-buttons {
line-height: 40px;
.btn:not(.dropdown-toggle) {
margin-left: 10px;
}
}
......@@ -7,7 +7,8 @@ module CreatesCommit
commit_params = @commit_params.merge(
source_project: @project,
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
......
......@@ -38,6 +38,12 @@ class Projects::BlobController < Projects::ApplicationController
end
def update
if params[:file_path].present?
@previous_path = @path
@path = params[:file_path]
@commit_params[:file_path] = @path
end
after_edit_path =
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) +
......
class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry, :play]
before_action :authorize_update_build!, except: [:index, :show, :status, :raw]
layout 'project'
......@@ -49,14 +49,19 @@ class Projects::BuildsController < Projects::ApplicationController
end
def retry
unless @build.retryable?
return render_404
end
return render_404 unless @build.retryable?
build = Ci::Build.retry(@build, current_user)
redirect_to build_path(build)
end
def play
return render_404 unless @build.playable?
build = @build.play(current_user)
redirect_to build_path(build)
end
def cancel
@build.cancel
redirect_to build_path(@build)
......
......@@ -26,18 +26,20 @@ module CiStatusHelper
icon_name =
case status
when 'success'
'check'
'icon_status_success'
when 'success_with_warnings'
'icon_status_warning'
when 'failed'
'close'
'icon_status_failed'
when 'pending'
'clock-o'
'icon_status_pending'
when 'running'
'spinner'
'icon_status_running'
else
'circle'
'icon_status_cancel'
end
icon(icon_name + ' fw')
custom_icon(icon_name)
end
def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '')
......
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)
minutes = interval_in_seconds / 60
seconds = interval_in_seconds - minutes * 60
......@@ -25,9 +15,19 @@ module TimeHelper
end
def duration_in_numbers(finished_at, started_at)
diff_in_seconds = finished_at.to_i - started_at.to_i
time_format = diff_in_seconds < 1.hour ? "%M:%S" : "%H:%M:%S"
interval = interval_in_seconds(started_at, finished_at)
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
......@@ -15,6 +15,7 @@ module Ci
scope :with_artifacts, ->() { where.not(artifacts_file: nil) }
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
scope :manual_actions, ->() { where(when: :manual) }
mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader
......@@ -91,6 +92,29 @@ module Ci
end
end
def manual?
self.when == 'manual'
end
def other_actions
pipeline.manual_actions.where.not(id: self)
end
def playable?
project.builds_enabled? && commands.present? && manual?
end
def play(current_user = nil)
# Try to queue a current build
if self.queue
self.update(user: current_user)
self
else
# Otherwise we need to create a duplicate
Ci::Build.retry(self, current_user)
end
end
def retryable?
project.builds_enabled? && commands.present? && complete?
end
......@@ -121,12 +145,7 @@ module Ci
end
def variables
variables = []
variables += predefined_variables
variables += yaml_variables if yaml_variables
variables += project_variables
variables += trigger_variables
variables
predefined_variables + yaml_variables + project_variables + trigger_variables
end
def merge_request
......@@ -385,6 +404,14 @@ module Ci
self.update(artifacts_expire_at: nil)
end
def when
read_attribute(:when) || build_attributes_from_config[:when] || 'on_success'
end
def yaml_variables
read_attribute(:yaml_variables) || build_attributes_from_config[:yaml_variables] || []
end
private
def update_artifacts_size
......@@ -427,5 +454,11 @@ module Ci
variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request
variables
end
def build_attributes_from_config
return {} unless pipeline.config_processor
pipeline.config_processor.build_attributes(name)
end
end
end
......@@ -69,6 +69,10 @@ module Ci
!tag?
end
def manual_actions
builds.latest.manual_actions
end
def retryable?
builds.latest.any? do |build|
build.failed? && build.retryable?
......
......@@ -22,6 +22,10 @@ class CommitStatus < ActiveRecord::Base
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
state_machine :status, initial: :pending do
event :queue do
transition skipped: :pending
end
event :run do
transition pending: :running
end
......
......@@ -16,10 +16,10 @@ module Statuseable
deduce_status = "(CASE
WHEN (#{builds})=0 THEN NULL
WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success'
WHEN (#{builds})=(#{pending}) THEN 'pending'
WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored}) THEN 'canceled'
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending'
WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
WHEN (#{running})+(#{pending})>0 THEN 'running'
ELSE 'failed'
END)"
......
......@@ -32,4 +32,8 @@ class Deployment < ActiveRecord::Base
def keep_around_commit
project.repository.keep_around(self.sha)
end
def manual_actions
deployable.try(:other_actions)
end
end
......@@ -733,6 +733,33 @@ class Repository
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)
commit_with_hooks(user, branch) do |ref|
committer = user_to_committer(user)
......
......@@ -854,7 +854,7 @@ class User < ActiveRecord::Base
groups.joins(:shared_projects).select(:project_id)]
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) }
end
......
......@@ -15,7 +15,7 @@ module Ci
status == 'success'
when 'on_failure'
status == 'failed'
when 'always'
when 'always', 'manual'
%w(success failed).include?(status)
end
end
......@@ -47,6 +47,10 @@ module Ci
user: user,
project: @pipeline.project)
# TODO: The proper implementation for this is in
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5295
build_attrs[:status] = 'skipped' if build_attrs[:when] == 'manual'
##
# We do not persist new builds here.
# Those will be persisted when @pipeline is saved.
......
......@@ -9,12 +9,14 @@ module Files
@commit_message = params[:commit_message]
@file_path = params[:file_path]
@previous_path = params[:previous_path]
@file_content = if params[:file_content_encoding] == 'base64'
Base64.decode64(params[:file_content])
else
params[:file_content]
end
# Validate parameters
validate
# Create new branch if it different from source_branch
......
......@@ -3,7 +3,10 @@ require_relative "base_service"
module Files
class UpdateService < Files::BaseService
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
......@@ -9,7 +9,7 @@ module Projects
private
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)
notify_success
else
......@@ -21,6 +21,10 @@ module Projects
Gitlab::ImportExport::VersionSaver.new(shared: @shared)
end
def avatar_saver
Gitlab::ImportExport::AvatarSaver.new(project: project, shared: @shared)
end
def project_tree_saver
Gitlab::ImportExport::ProjectTreeSaver.new(project: project, shared: @shared)
end
......
......@@ -14,4 +14,8 @@ class AvatarUploader < CarrierWave::Uploader::Base
def reset_events_cache(file)
model.reset_events_cache if model.is_a?(User)
end
def exists?
model.avatar.file && model.avatar.file.exists?
end
end
......@@ -4,7 +4,9 @@
= icon('code-fork')
= ref
%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)
%span.editor-file-name
......
......@@ -49,7 +49,7 @@
- if @build.duration
%p.build-detail-row
%span.build-light-text Duration:
#{duration_in_words(@build.finished_at, @build.started_at)}
= time_interval_in_words(@build.duration)
- if @build.finished_at
%p.build-detail-row
%span.build-light-text Finished:
......
......@@ -2,7 +2,7 @@
- if current_user && can?(current_user, :fork_project, @project)
- 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
= icon('code-fork fw')
= custom_icon('icon_fork')
Fork
%div.count-with-arrow
%span.arrow
......@@ -10,7 +10,7 @@
= @project.forks_count
- else
= 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
%div.count-with-arrow
%span.arrow
......
......@@ -39,6 +39,8 @@
%span.label.label-danger allowed to fail
- if defined?(retried) && retried
%span.label.label-warning retried
- if build.manual?
%span.label.label-info manual
- if defined?(runner) && runner
......@@ -79,6 +81,11 @@
- if build.active?
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
= icon('remove', class: 'cred')
- elsif defined?(allow_retry) && allow_retry && build.retryable?
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
= icon('repeat')
- elsif defined?(allow_retry) && allow_retry
- if build.retryable?
= link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
= icon('repeat')
- elsif build.playable?
= link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
= icon('play')
......@@ -10,12 +10,13 @@
= link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
%span ##{pipeline.id}
- if pipeline.ref
.icon-container
= pipeline.tag? ? icon('tag') : icon('code-fork')
= link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace branch-name"
= custom_icon("icon_commit")
.icon-container
= custom_icon("icon_commit")
= link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace"
- if pipeline.tag?
%span.label.label-primary tag
- elsif pipeline.latest?
- if pipeline.latest?
%span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
- if pipeline.triggered?
%span.label.label-primary triggered
......@@ -34,7 +35,7 @@
- stages_status = pipeline.statuses.latest.stages_status
- stages.each do |stage|
%td
%td.stage-cell
- status = stages_status[stage]
- tooltip = "#{stage.titleize}: #{status || 'not found'}"
- if status
......@@ -57,18 +58,31 @@
%td.pipeline-actions
.controls.hidden-xs.pull-right
- artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
- if artifacts.present?
.inline
.btn-group
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
= icon("download")
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- artifacts.each do |build|
%li
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
= icon("download")
%span Download '#{build.name}' artifacts
- actions = pipeline.manual_actions
- if artifacts.present? || actions.any?
.btn-group.inline
- if actions.any?
.btn-group
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
= icon("play")
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |build|
%li
= link_to play_namespace_project_build_path(@project.namespace, @project, build), method: :post, rel: 'nofollow' do
= icon("play")
%span= build.name.humanize
- if artifacts.present?
.btn-group
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
= icon("download")
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- artifacts.each do |build|
%li
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
= icon("download")
%span Download '#{build.name}' artifacts
- if can?(current_user, :update_pipeline, @project)
.cancel-retry-btns.inline
......
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
.pull-right
- actions = deployment.manual_actions
- if actions.present?
.btn-group.inline
.btn-group
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
= icon("play")
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |action|
%li
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
= icon("play")
%span= action.name.humanize
- if local_assigns.fetch(:allow_rollback, false)
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do
- if deployment.last?
Retry
- else
Rollback
......@@ -7,17 +7,11 @@
%td
- if deployment.deployable
= link_to namespace_project_build_path(@project.namespace, @project, deployment.deployable) do
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable] do
= "#{deployment.deployable.name} (##{deployment.deployable.id})"
%td
#{time_ago_with_tooltip(deployment.created_at)}
%td
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
.pull-right
= link_to retry_namespace_project_build_path(@project.namespace, @project, deployment.deployable), method: :post, class: 'btn btn-build' do
- if deployment.last?
Retry
- else
Rollback
= render 'projects/deployments/actions', deployment: deployment, allow_rollback: true
......@@ -15,3 +15,6 @@
%td
- if last_deployment
#{time_ago_with_tooltip(last_deployment.created_at)}
%td
= render 'projects/deployments/actions', deployment: last_deployment
......@@ -28,4 +28,5 @@
%th Environment
%th Last deployment
%th Date
%th
= render @environments
......@@ -5,7 +5,7 @@
%div{ class: container_class }
.top-area
.col-md-9
%h3.page-title= @environment.name.titleize
%h3.page-title= @environment.name.capitalize
.col-md-3
.nav-controls
......
......@@ -31,11 +31,11 @@
- if current_user && can?(current_user, :fork_project, @project)
- 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
= icon('code-fork fw')
= custom_icon('icon_fork')
Fork
- else
= 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
......
......@@ -51,7 +51,7 @@
%td.duration
- if generic_commit_status.duration
= 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
- if generic_commit_status.finished_at
......
......@@ -14,9 +14,9 @@
.disabled-comment.text-center
.disabled-comment-text.inline
Please
= link_to "register",new_user_session_path
= link_to "register", new_session_path(:user, redirect_to_referer: 'yes')
or
= link_to "login",new_user_session_path
= link_to "login", new_session_path(:user, redirect_to_referer: 'yes')
to post a comment
:javascript
......
- @no_container = true
- page_title @tag.name, "Tags"
= render "projects/commits/head"
.row-content-block
.pull-right
- 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
= icon("pencil")
= link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse files' do
= icon('files-o')
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse commits' do
= icon('history')
- if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: @tag.name, project: @project
- if can?(current_user, :admin_project, @project)
.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
%i.fa.fa-trash-o
.title
%span.item-title= @tag.name
- if @commit
= render 'projects/branches/commit', commit: @commit, project: @project
- else
Cant find HEAD commit for this tag
- if @tag.message.present?
%pre.body
= strip_gpg_signature(@tag.message)
%div{ class: container_class }
.sub-header-block
.pull-right.tag-buttons
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Edit release notes' do
= icon("pencil")
= link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse files' do
= icon('files-o')
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
= icon('history')
- if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: @tag.name, project: @project
- if can?(current_user, :admin_project, @project)
.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
%i.fa.fa-trash-o
.tag-info.append-bottom-10
.title
%span.item-title= @tag.name
- if @commit
= render 'projects/branches/commit', commit: @commit, project: @project
- else
Cant find HEAD commit for this tag
- if @tag.message.present?
%pre.body
= strip_gpg_signature(@tag.message)
.append-bottom-default.prepend-top-default
- if @release.description.present?
.description
.wiki
= preserve do
= markdown @release.description
- else
This tag has no release notes.
.append-bottom-default.prepend-top-default
- if @release.description.present?
.description
.wiki
= preserve do
= markdown @release.description
- else
This tag has no release notes.
<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
mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/.match(request.path_info) }, via: [:get, :post, :put]
# Help
get 'help' => 'help#index'
get 'help/*path' => 'help#show', as: :help_page
get 'help/shortcuts'
get 'help/ui' => 'help#ui'
get 'help' => 'help#index'
get 'help/shortcuts' => 'help#shortcuts'
get 'help/ui' => 'help#ui'
get 'help/*path' => 'help#show', as: :help_page
#
# Global snippets
......@@ -755,6 +754,7 @@ Rails.application.routes.draw do
get :status
post :cancel
post :retry
post :play
post :erase
get :trace
get :raw
......
class Gitlab::Seeder::Builds
STAGES = %w[build notify_build test notify_test deploy notify_deploy]
def initialize(project)
@project = project
end
def seed!
ci_commits.each do |ci_commit|
pipelines.each do |pipeline|
begin
build_create!(ci_commit, name: 'test build 1')
build_create!(ci_commit, status: 'success', name: 'test build 2')
build_create!(pipeline, name: 'build:linux', stage: 'build')
build_create!(pipeline, name: 'build:osx', stage: 'build')
build_create!(pipeline, name: 'slack post build', stage: 'notify_build')
build_create!(pipeline, name: 'rspec:linux', stage: 'test')
build_create!(pipeline, name: 'rspec:windows', stage: 'test')
build_create!(pipeline, name: 'rspec:windows', stage: 'test')
build_create!(pipeline, name: 'rspec:osx', stage: 'test')
build_create!(pipeline, name: 'spinach:linux', stage: 'test')
build_create!(pipeline, name: 'spinach:osx', stage: 'test')
build_create!(pipeline, name: 'cucumber:linux', stage: 'test')
build_create!(pipeline, name: 'cucumber:osx', stage: 'test')
build_create!(pipeline, name: 'slack post test', stage: 'notify_test')
build_create!(pipeline, name: 'staging', stage: 'deploy', environment: 'staging')
build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual')
commit_status_create!(pipeline, name: 'jenkins')
print '.'
rescue ActiveRecord::RecordInvalid
print 'F'
......@@ -15,8 +36,8 @@ class Gitlab::Seeder::Builds
end
end
def ci_commits
commits = @project.repository.commits('master', nil, 5)
def pipelines
commits = @project.repository.commits('master', limit: 5)
commits_sha = commits.map { |commit| commit.raw.id }
commits_sha.map do |sha|
@project.ensure_pipeline(sha, 'master')
......@@ -25,11 +46,11 @@ class Gitlab::Seeder::Builds
[]
end
def build_create!(ci_commit, opts = {})
attributes = build_attributes_for(ci_commit).merge(opts)
def build_create!(pipeline, opts = {})
attributes = build_attributes_for(pipeline, opts)
build = Ci::Build.new(attributes)
if %w(success failed).include?(build.status)
if opts[:name].start_with?('build')
artifacts_cache_file(artifacts_archive_path) do |file|
build.artifacts_file = file
end
......@@ -40,19 +61,28 @@ class Gitlab::Seeder::Builds
end
build.save!
build.update(status: build_status)
if %w(running success failed).include?(build.status)
# We need to set build trace after saving a build (id required)
build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
end
end
def commit_status_create!(pipeline, opts = {})
attributes = commit_status_attributes_for(pipeline, opts)
GenericCommitStatus.create(attributes)
end
def commit_status_attributes_for(pipeline, opts)
{ name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]),
ref: 'master', user: build_user, project: @project, pipeline: pipeline,
created_at: Time.now, updated_at: Time.now
}.merge(opts)
end
def build_attributes_for(ci_commit)
{ name: 'test build', commands: "$ build command",
stage: 'test', stage_idx: 1, ref: 'master',
user_id: build_user, gl_project_id: @project.id,
status: build_status, commit_id: ci_commit.id,
created_at: Time.now, updated_at: Time.now }
def build_attributes_for(pipeline, opts)
commit_status_attributes_for(pipeline, opts).merge(commands: '$ build command')
end
def build_user
......@@ -63,13 +93,16 @@ class Gitlab::Seeder::Builds
Ci::Build::AVAILABLE_STATUSES.sample
end
def stage_index(stage)
STAGES.index(stage) || 0
end
def artifacts_archive_path
Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
end
def artifacts_metadata_path
Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz'
end
def artifacts_cache_file(file_path)
......
......@@ -485,6 +485,7 @@ failure.
1. `on_failure` - execute build only when at least one build from prior stages
fails.
1. `always` - execute build regardless of the status of builds from prior stages.
1. `manual` - execute build manually.
For example:
......@@ -516,6 +517,7 @@ deploy_job:
stage: deploy
script:
- make deploy
when: manual
cleanup_job:
stage: cleanup
......@@ -527,7 +529,20 @@ cleanup_job:
The above script will:
1. Execute `cleanup_build_job` only when `build_job` fails
2. Always execute `cleanup_job` as the last step in pipeline.
2. Always execute `cleanup_job` as the last step in pipeline
3. Allow you to manually execute `deploy_job` from GitLab
#### Manual actions
>**Note:**
Introduced in GitLab 8.10.
Manual actions are special type of jobs that are not executed automatically in pipeline.
They need to be explicitly started by the user.
Manual actions can be started from pipelines, builds, environments and deployments views.
You can execute the same manual action multiple times.
Example usage of manual actions is deployment, ex. promote a staging environment to production.
### environment
......
......@@ -167,3 +167,22 @@ of those assets. Unless you are modifying the JavaScript / CSS code on your
production machine after installing the package, there should be no reason to redo
rake assets:precompile on the production machine. If you suspect that assets
have been corrupted, you should reinstall the omnibus package.
## Tracking Deployments
GitLab provides a Rake task that lets you track deployments in GitLab
Performance Monitoring. This Rake task simply stores the current GitLab version
in the GitLab Performance Monitoring database.
For Omnibus-packages:
```
sudo gitlab-rake gitlab:track_deployment
```
For installations from source:
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production
```
......@@ -15,7 +15,8 @@ module API
# GET /projects/:id/repository/branches
get ":id/repository/branches" do
branches = user_project.repository.branches.sort_by(&:name)
present branches, with: Entities::RepoObject, project: user_project
present branches, with: Entities::RepoBranch, project: user_project
end
# Get a single branch
......@@ -28,7 +29,8 @@ module API
get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do
@branch = user_project.repository.branches.find { |item| item.name == params[:branch] }
not_found!("Branch") unless @branch
present @branch, with: Entities::RepoObject, project: user_project
present @branch, with: Entities::RepoBranch, project: user_project
end
# Protect a single branch
......@@ -60,7 +62,7 @@ module API
developers_can_merge: developers_can_merge || false)
end
present @branch, with: Entities::RepoObject, project: user_project
present @branch, with: Entities::RepoBranch, project: user_project
end
# Unprotect a single branch
......@@ -79,7 +81,7 @@ module API
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
protected_branch.destroy if protected_branch
present @branch, with: Entities::RepoObject, project: user_project
present @branch, with: Entities::RepoBranch, project: user_project
end
# Create branch
......@@ -97,7 +99,7 @@ module API
if result[:status] == :success
present result[:branch],
with: Entities::RepoObject,
with: Entities::RepoBranch,
project: user_project
else
render_api_error!(result[:message], 400)
......
......@@ -114,33 +114,23 @@ module API
end
end
class RepoObject < Grape::Entity
class RepoBranch < Grape::Entity
expose :name
expose :commit do |repo_obj, options|
if repo_obj.respond_to?(:commit)
repo_obj.commit
elsif options[:project]
options[:project].repository.commit(repo_obj.target)
end
expose :commit do |repo_branch, options|
options[:project].repository.commit(repo_branch.target)
end
expose :protected do |repo_obj, options|
if options[:project]
options[:project].protected_branch? repo_obj.name
end
expose :protected do |repo_branch, options|
options[:project].protected_branch? repo_branch.name
end
expose :developers_can_push do |repo_obj, options|
if options[:project]
options[:project].developers_can_push_to_protected_branch? repo_obj.name
end
expose :developers_can_push do |repo_branch, options|
options[:project].developers_can_push_to_protected_branch? repo_branch.name
end
expose :developers_can_merge do |repo_obj, options|
if options[:project]
options[:project].developers_can_merge_to_protected_branch? repo_obj.name
end
expose :developers_can_merge do |repo_branch, options|
options[:project].developers_can_merge_to_protected_branch? repo_branch.name
end
end
......@@ -437,27 +427,14 @@ module API
end
class RepoTag < Grape::Entity
expose :name
expose :message do |repo_obj, _options|
if repo_obj.respond_to?(:message)
repo_obj.message
else
nil
end
end
expose :name, :message
expose :commit do |repo_obj, options|
if repo_obj.respond_to?(:commit)
repo_obj.commit
elsif options[:project]
options[:project].repository.commit(repo_obj.target)
end
expose :commit do |repo_tag, options|
options[:project].repository.commit(repo_tag.target)
end
expose :release, using: Entities::Release do |repo_obj, options|
if options[:project]
options[:project].releases.find_by(tag: repo_obj.name)
end
expose :release, using: Entities::Release do |repo_tag, options|
options[:project].releases.find_by(tag: repo_tag.name)
end
end
......
......@@ -44,23 +44,51 @@ module Ci
end
def builds_for_ref(ref, tag = false, trigger_request = nil)
jobs_for_ref(ref, tag, trigger_request).map do |name, job|
build_job(name, job)
jobs_for_ref(ref, tag, trigger_request).map do |name, _|
build_attributes(name)
end
end
def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
jobs_for_stage_and_ref(stage, ref, tag, trigger_request).map do |name, job|
build_job(name, job)
jobs_for_stage_and_ref(stage, ref, tag, trigger_request).map do |name, _|
build_attributes(name)
end
end
def builds
@jobs.map do |name, job|
build_job(name, job)
@jobs.map do |name, _|
build_attributes(name)
end
end
def build_attributes(name)
job = @jobs[name.to_sym] || {}
{
stage_idx: @stages.index(job[:stage]),
stage: job[:stage],
##
# Refactoring note:
# - before script behaves differently than after script
# - after script returns an array of commands
# - before script should be a concatenated command
commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
tag_list: job[:tags] || [],
name: name,
allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success',
environment: job[:environment],
yaml_variables: yaml_variables(name),
options: {
image: job[:image] || @image,
services: job[:services] || @services,
artifacts: job[:artifacts],
cache: job[:cache] || @cache,
dependencies: job[:dependencies],
after_script: job[:after_script] || @after_script,
}.compact
}
end
private
def initial_parsing
......@@ -89,33 +117,6 @@ module Ci
@jobs[name] = { stage: stage }.merge(job)
end
def build_job(name, job)
{
stage_idx: @stages.index(job[:stage]),
stage: job[:stage],
##
# Refactoring note:
# - before script behaves differently than after script
# - after script returns an array of commands
# - before script should be a concatenated command
commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
tag_list: job[:tags] || [],
name: name,
allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success',
environment: job[:environment],
yaml_variables: yaml_variables(name),
options: {
image: job[:image] || @image,
services: job[:services] || @services,
artifacts: job[:artifacts],
cache: job[:cache] || @cache,
dependencies: job[:dependencies],
after_script: job[:after_script] || @after_script,
}.compact
}
end
def yaml_variables(name)
variables = global_variables.merge(job_variables(name))
variables.map do |key, value|
......@@ -194,8 +195,8 @@ module Ci
raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
end
if job[:when] && !job[:when].in?(%w[on_success on_failure always])
raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always"
if job[:when] && !job[:when].in?(%w[on_success on_failure always manual])
raise ValidationError, "#{name} job: when parameter should be on_success, on_failure, always or manual"
end
if job[:environment] && !validate_environment(job[:environment])
......
......@@ -8,8 +8,8 @@ module Gitlab
if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
false
else
missed_refs, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev}))
missed_refs.split("\n").size > 0
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_ref.present?
end
end
end
......
......@@ -110,6 +110,7 @@ module Gitlab
def deploy_key_can_read_project?
if deploy_key
return true if project.public?
deploy_key.projects.include?(project)
else
false
......
......@@ -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.max_file_size = current_application_settings.max_attachment_size
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.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
def git_bin_path
Gitlab.config.git.bin_path
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
......@@ -9,7 +9,7 @@ module Gitlab
end
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
else
raise Projects::ImportService::Error.new(@shared.errors.join(', '))
......@@ -35,6 +35,10 @@ module Gitlab
project: @project)
end
def avatar_restorer
Gitlab::ImportExport::AvatarRestorer.new(project: project_tree.restored_project, shared: @shared)
end
def repo_restorer
Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: repo_path,
shared: @shared,
......
module Gitlab
module ImportExport
class UploadsSaver
include Gitlab::ImportExport::CommandLineUtil
def initialize(project:, shared:)
@project = project
@shared = shared
......@@ -17,12 +19,6 @@ module Gitlab
private
def copy_files(source, destination)
FileUtils.mkdir_p(destination)
FileUtils.copy_entry(source, destination)
true
end
def uploads_export_path
File.join(@shared.export_path, 'uploads')
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
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
......@@ -3,6 +3,8 @@ include ActionDispatch::TestProcess
FactoryGirl.define do
factory :ci_build, class: Ci::Build do
name 'test'
stage 'test'
stage_idx 0
ref 'master'
tag false
created_at 'Di 29. Okt 09:50:00 CET 2013'
......@@ -43,6 +45,11 @@ FactoryGirl.define do
status 'pending'
end
trait :manual do
status 'skipped'
self.when 'manual'
end
trait :allowed_to_fail do
allow_failure true
end
......
......@@ -13,6 +13,7 @@ feature 'Environments', feature: true do
describe 'when showing environments' do
given!(:environment) { }
given!(:deployment) { }
given!(:manual) { }
before do
visit namespace_project_environments_path(project.namespace, project)
......@@ -43,6 +44,24 @@ feature 'Environments', feature: true do
scenario 'does show deployment SHA' do
expect(page).to have_link(deployment.short_sha)
end
context 'with build and manual actions' do
given(:pipeline) { create(:ci_pipeline, project: project) }
given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
scenario 'does show a play button' do
expect(page).to have_link(manual.name.humanize)
end
scenario 'does allow to play manual action' do
expect(manual).to be_skipped
expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
expect(page).to have_content(manual.name)
expect(manual.reload).to be_pending
end
end
end
end
......@@ -54,6 +73,7 @@ feature 'Environments', feature: true do
describe 'when showing the environment' do
given(:environment) { create(:environment, project: project) }
given!(:deployment) { }
given!(:manual) { }
before do
visit namespace_project_environment_path(project.namespace, project, environment)
......@@ -77,7 +97,8 @@ feature 'Environments', feature: true do
end
context 'with build' do
given(:build) { create(:ci_build, project: project) }
given(:pipeline) { create(:ci_pipeline, project: project) }
given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
scenario 'does show build name' do
......@@ -87,6 +108,21 @@ feature 'Environments', feature: true do
scenario 'does show retry button' do
expect(page).to have_link('Retry')
end
context 'with manual action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
scenario 'does show a play button' do
expect(page).to have_link(manual.name.humanize)
end
scenario 'does allow to play manual action' do
expect(manual).to be_skipped
expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
expect(page).to have_content(manual.name)
expect(manual.reload).to be_pending
end
end
end
end
end
......
......@@ -63,6 +63,20 @@ feature "Pipelines", feature: true do
end
end
context 'with manual actions' do
let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'manual build', stage: 'test', commands: 'test') }
before { visit namespace_project_pipelines_path(project.namespace, project) }
it { expect(page).to have_link('Manual build') }
context 'when playing' do
before { click_link('Manual build') }
it { expect(manual.reload).to be_pending }
end
end
context 'for generic statuses' do
context 'when running' do
let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') }
......@@ -118,6 +132,7 @@ feature "Pipelines", feature: true do
@success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
@failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
@running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
@manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build')
@external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
end
......@@ -132,6 +147,7 @@ feature "Pipelines", feature: true do
expect(page).to have_content(@external.id)
expect(page).to have_content('Retry failed')
expect(page).to have_content('Cancel running')
expect(page).to have_link('Play')
end
context 'retrying builds' do
......@@ -155,6 +171,12 @@ feature "Pipelines", feature: true do
it { expect(page).to have_selector('.ci-canceled') }
end
end
context 'playing manual build' do
before { click_link('Play') }
it { expect(@manual.reload).to be_pending }
end
end
describe 'POST /:project/pipelines' do
......
......@@ -7,7 +7,13 @@ describe CiStatusHelper do
let(:failed_commit) { double("Ci::Pipeline", status: 'failed') }
describe 'ci_icon_for_status' do
it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') }
it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') }
it 'renders to correct svg on success' do
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
require 'spec_helper'
describe TimeHelper do
describe "#duration_in_words" do
describe "#time_interval_in_words" do
it "returns minutes and seconds" do
intervals_in_words = {
100 => "1 minute 40 seconds",
......@@ -11,26 +11,23 @@ describe TimeHelper do
}
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
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
describe "#time_interval_in_words" do
describe "#duration_in_numbers" do
it "returns minutes and seconds" do
intervals_in_words = {
100 => "1 minute 40 seconds",
121 => "2 minutes 1 second",
3721 => "62 minutes 1 second",
0 => "0 seconds"
duration_in_numbers = {
[100, 0] => "01:40",
[121, 0] => "02:01",
[3721, 0] => "01:02:01",
[0, 0] => "00:00",
[nil, Time.now.to_i - 42] => "00:42"
}
intervals_in_words.each do |interval, expectation|
expect(time_interval_in_words(interval)).to eq(expectation)
duration_in_numbers.each do |interval, expectation|
expect(duration_in_numbers(*interval)).to eq(expectation)
end
end
end
......
......@@ -1141,7 +1141,7 @@ EOT
config = YAML.dump({ rspec: { script: "test", when: 1 } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure, always or manual")
end
it "returns errors if job artifacts:name is not an a string" do
......
......@@ -113,7 +113,7 @@ describe Gitlab::Badge::Build do
sha: sha,
ref: branch)
create(:ci_build, pipeline: pipeline)
create(:ci_build, pipeline: pipeline, stage: 'notify')
end
def status_node(data, status)
......
......@@ -44,12 +44,12 @@ describe Gitlab::GitAccess, lib: true do
end
describe 'download_access_check' do
subject { access.check('git-upload-pack') }
describe 'master permissions' do
before { project.team << [user, :master] }
context 'pull code' do
subject { access.download_access_check }
it { expect(subject.allowed?).to be_truthy }
end
end
......@@ -58,8 +58,6 @@ describe Gitlab::GitAccess, lib: true do
before { project.team << [user, :guest] }
context 'pull code' do
subject { access.download_access_check }
it { expect(subject.allowed?).to be_falsey }
end
end
......@@ -71,16 +69,12 @@ describe Gitlab::GitAccess, lib: true do
end
context 'pull code' do
subject { access.download_access_check }
it { expect(subject.allowed?).to be_falsey }
end
end
describe 'without acccess to project' do
context 'pull code' do
subject { access.download_access_check }
it { expect(subject.allowed?).to be_falsey }
end
end
......@@ -90,10 +84,31 @@ describe Gitlab::GitAccess, lib: true do
let(:actor) { key }
context 'pull code' do
before { key.projects << project }
subject { access.download_access_check }
context 'when project is authorized' do
before { key.projects << project }
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
......@@ -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 }))
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
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
end
describe '#variables' do
context 'returns variables' do
subject { build.variables }
let(:predefined_variables) do
[
{ key: :CI_BUILD_NAME, value: 'test', public: true },
{ key: :CI_BUILD_STAGE, value: 'test', public: true },
]
end
let(:predefined_variables) do
[
{ key: :CI_BUILD_NAME, value: 'test', public: true },
{ key: :CI_BUILD_STAGE, value: 'stage', public: true },
]
end
subject { build.variables }
context 'returns variables' do
let(:yaml_variables) do
[
{ key: :DB_NAME, value: 'postgres', public: true }
......@@ -208,7 +208,7 @@ describe Ci::Build, models: true do
end
before do
build.update_attributes(stage: 'stage', yaml_variables: yaml_variables)
build.yaml_variables = yaml_variables
end
it { is_expected.to eq(predefined_variables + yaml_variables) }
......@@ -262,6 +262,54 @@ describe Ci::Build, models: true do
end
end
end
context 'when yaml_variables is undefined' do
before do
build.yaml_variables = nil
end
context 'use from gitlab-ci.yml' do
before do
stub_ci_pipeline_yaml_file(config)
end
context 'if config is not found' do
let(:config) { nil }
it { is_expected.to eq(predefined_variables) }
end
context 'if config does not have a questioned job' do
let(:config) do
YAML.dump({
test_other: {
script: 'Hello World'
}
})
end
it { is_expected.to eq(predefined_variables) }
end
context 'if config has variables' do
let(:config) do
YAML.dump({
test: {
script: 'Hello World',
variables: {
KEY: 'value'
}
}
})
end
let(:variables) do
[{ key: :KEY, value: 'value', public: true }]
end
it { is_expected.to eq(predefined_variables + variables) }
end
end
end
end
describe '#has_tags?' do
......@@ -670,4 +718,120 @@ describe Ci::Build, models: true do
end
end
end
describe '#manual?' do
before do
build.update(when: value)
end
subject { build.manual? }
context 'when is set to manual' do
let(:value) { 'manual' }
it { is_expected.to be_truthy }
end
context 'when set to something else' do
let(:value) { 'something else' }
it { is_expected.to be_falsey }
end
end
describe '#other_actions' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') }
subject { build.other_actions }
it 'returns other actions' do
is_expected.to contain_exactly(other_build)
end
end
describe '#play' do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
subject { build.play }
it 'enques a build' do
is_expected.to be_pending
is_expected.to eq(build)
end
context 'for success build' do
before { build.queue }
it 'creates a new build' do
is_expected.to be_pending
is_expected.not_to eq(build)
end
end
end
describe '#when' do
subject { build.when }
context 'if is undefined' do
before do
build.when = nil
end
context 'use from gitlab-ci.yml' do
before do
stub_ci_pipeline_yaml_file(config)
end
context 'if config is not found' do
let(:config) { nil }
it { is_expected.to eq('on_success') }
end
context 'if config does not have a questioned job' do
let(:config) do
YAML.dump({
test_other: {
script: 'Hello World'
}
})
end
it { is_expected.to eq('on_success') }
end
context 'if config has when' do
let(:config) do
YAML.dump({
test: {
script: 'Hello World',
when: 'always'
}
})
end
it { is_expected.to eq('always') }
end
end
end
end
describe '#retryable?' do
context 'when build is running' do
before { build.run! }
it 'should return false' do
expect(build.retryable?).to be false
end
end
context 'when build is finished' do
before { build.success! }
it 'should return true' do
expect(build.retryable?).to be true
end
end
end
end
......@@ -260,6 +260,68 @@ describe Ci::Pipeline, models: true do
expect(pipeline.reload.status).to eq('canceled')
end
end
context 'when listing manual actions' do
let(:yaml) do
{
stages: ["build", "test", "test_failure", "deploy", "cleanup"],
build: {
stage: "build",
script: "BUILD",
},
test: {
stage: "test",
script: "TEST",
},
test_failure: {
stage: "test_failure",
script: "ON test failure",
when: "on_failure",
},
deploy: {
stage: "deploy",
script: "PUBLISH",
},
production: {
stage: "deploy",
script: "PUBLISH",
when: "manual",
},
cleanup: {
stage: "cleanup",
script: "TIDY UP",
when: "always",
},
clear_cache: {
stage: "cleanup",
script: "CLEAR CACHE",
when: "manual",
}
}
end
it 'returns only for skipped builds' do
# currently all builds are created
expect(create_builds).to be_truthy
expect(manual_actions).to be_empty
# succeed stage build
pipeline.builds.running_or_pending.each(&:success)
expect(manual_actions).to be_empty
# succeed stage test
pipeline.builds.running_or_pending.each(&:success)
expect(manual_actions).to be_one # production
# succeed stage deploy
pipeline.builds.running_or_pending.each(&:success)
expect(manual_actions).to be_many # production and clear cache
end
def manual_actions
pipeline.manual_actions
end
end
end
context 'when no builds created' do
......@@ -416,4 +478,28 @@ describe Ci::Pipeline, models: true do
end
end
end
describe '#manual_actions' do
subject { pipeline.manual_actions }
it 'when none defined' do
is_expected.to be_empty
end
context 'when action defined' do
let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }
it 'returns one action' do
is_expected.to contain_exactly(manual)
end
context 'there are multiple of the same name' do
let!(:manual2) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }
it 'returns latest one' do
is_expected.to contain_exactly(manual2)
end
end
end
end
end
......@@ -11,6 +11,7 @@ describe Deployment, models: true do
it { is_expected.to delegate_method(:name).to(:environment).with_prefix }
it { is_expected.to delegate_method(:commit).to(:project) }
it { is_expected.to delegate_method(:commit_title).to(:commit).as(:try) }
it { is_expected.to delegate_method(:manual_actions).to(:deployable).as(:try) }
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to validate_presence_of(:sha) }
......
......@@ -130,6 +130,36 @@ describe Repository, models: true do
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
let(:results) { repository.search_files('feature', 'master') }
subject { results }
......
......@@ -887,16 +887,25 @@ describe User, models: true do
end
describe '#authorized_projects' do
let!(:user) { create(:user) }
let!(:private_project) { create(:project, :private) }
context 'with a minimum access level' do
it 'includes projects for which the user is an owner' do
user = create(:user)
project = create(:empty_project, :private, namespace: user.namespace)
before do
private_project.team << [user, Gitlab::Access::MASTER]
end
expect(user.authorized_projects(Gitlab::Access::REPORTER))
.to contain_exactly(project)
end
subject { user.authorized_projects }
it 'includes projects for which the user is a master' do
user = create(:user)
project = create(:empty_project, :private)
project.team << [user, Gitlab::Access::MASTER]
it { is_expected.to eq([private_project]) }
expect(user.authorized_projects(Gitlab::Access::REPORTER))
.to contain_exactly(project)
end
end
end
describe '#ci_authorized_runners' do
......
......@@ -116,12 +116,9 @@ describe HelpController, "routing" do
expect(get(path)).to route_to('help#show',
path: 'workflow/protected_branches/protected_branches1',
format: 'png')
path = '/help/shortcuts'
expect(get(path)).to route_to('help#show',
path: 'shortcuts')
path = '/help/ui'
expect(get(path)).to route_to('help#show',
path: 'ui')
expect(get(path)).to route_to('help#ui')
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