Skip to content
Snippets Groups Projects
Commit feec3a45 authored by Kamil Trzcińśki's avatar Kamil Trzcińśki
Browse files

Show pipelines

parent 95b010fb
No related branches found
No related tags found
No related merge requests found
Showing
with 228 additions and 353 deletions
Loading
Loading
@@ -20,6 +20,30 @@ class Projects::BuildsController < Projects::ApplicationController
@builds = @builds.page(params[:page]).per(30)
end
 
def commits
@scope = params[:scope]
@all_commits = project.ci_commits
@commits = @all_commits.order(id: :desc)
@commits =
case @scope
when 'latest'
@commits
when 'branches'
refs = project.repository.branches.map(&:name)
ids = @all_commits.where(ref: refs).group(:ref).select('max(id)')
@commits.where(id: ids)
when 'tags'
refs = project.repository.tags.map(&:name)
ids = @all_commits.where(ref: refs).group(:ref).select('max(id)')
@commits.where(id: ids)
else
@commits
end
@commits = @commits.page(params[:page]).per(30)
end
private
def cancel_all
@project.builds.running_or_pending.each(&:cancel)
redirect_to namespace_project_builds_path(project.namespace, project)
Loading
Loading
@@ -68,6 +92,10 @@ class Projects::BuildsController < Projects::ApplicationController
@build ||= project.builds.unscoped.find_by!(id: params[:id])
end
 
def ci_commit
@ci_commit ||= project.ci_commits.find_by!(id: params[:id])
end
def build_path(build)
namespace_project_build_path(build.project.namespace, build.project, build)
end
Loading
Loading
class Projects::CiCommitsController < Projects::ApplicationController
before_action :ci_commit, except: [:index]
before_action :authorize_read_build!
before_action :ci_commit, except: [:index, :new, :create]
before_action :authorize_read_pipeline!
before_action :authorize_create_pipeline!, only: [:new, :create]
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
layout 'project'
 
def index
Loading
Loading
@@ -11,6 +13,8 @@ class Projects::CiCommitsController < Projects::ApplicationController
case @scope
when 'latest'
@commits
when 'running'
@commits.running_or_pending
when 'branches'
refs = project.repository.branches.map(&:name)
ids = @all_commits.where(ref: refs).group(:ref).select('max(id)')
Loading
Loading
@@ -25,6 +29,47 @@ class Projects::CiCommitsController < Projects::ApplicationController
@commits = @commits.page(params[:page]).per(30)
end
 
def new
end
def create
ref_names = project.repository.ref_names
unless ref_names.include?(params[:ref])
@error = 'Reference not found'
render action: 'new'
return
end
commit = project.commit(params[:ref])
unless commit
@error = 'Commit not found'
render action: 'new'
return
end
ci_commit = project.ci_commit(commit.id, params[:ref])
if ci_commit
@error = 'Pipeline already created'
render action: 'new'
return
end
# Skip creating ci_commit when no gitlab-ci.yml is found
commit = project.ci_commits.new(sha: commit.id, ref: params[:ref], before_sha: Gitlab::Git::BLANK_SHA)
unless commit.config_processor
@error = commit.yaml_errors || 'Missing .gitlab-ci.yml file'
render action: 'new'
return
end
Ci::Commit.transaction do
commit.save!
commit.create_builds(params[:ref], false, current_user)
end
redirect_to builds_namespace_project_commit_path(project.namespace, project, commit.id)
end
def show
@commit = @ci_commit.commit
@builds = @ci_commit.builds
Loading
Loading
@@ -35,6 +80,20 @@ class Projects::CiCommitsController < Projects::ApplicationController
end
end
 
def retry
ci_commit.builds.latest.failed.select(&:retryable?).each(&:retry)
redirect_back_or_default default: namespace_project_ci_commits_path(project.namespace, project)
end
def cancel
ci_commit.builds.running_or_pending.each(&:cancel)
redirect_back_or_default default: namespace_project_ci_commits_path(project.namespace, project)
end
def retry_builds
end
private
 
def ci_commit
Loading
Loading
Loading
Loading
@@ -96,6 +96,11 @@ class Projects::CommitController < Projects::ApplicationController
 
def ci_commits
@ci_commits ||= project.ci_commits.where(sha: commit.sha)
if params[:commit_id]
@ci_commits.where(id: params[:commit_id].to_i)
else
@ci_commits
end
end
 
def define_show_vars
Loading
Loading
Loading
Loading
@@ -195,6 +195,7 @@ class Ability
:admin_label,
:read_commit_status,
:read_build,
:read_pipeline,
]
end
 
Loading
Loading
@@ -206,6 +207,8 @@ class Ability
:update_commit_status,
:create_build,
:update_build,
:create_pipeline,
:update_pipeline,
:create_merge_request,
:create_wiki,
:push_code
Loading
Loading
@@ -234,7 +237,8 @@ class Ability
:admin_wiki,
:admin_project,
:admin_commit_status,
:admin_build
:admin_build,
:admin_pipeline
]
end
 
Loading
Loading
@@ -277,6 +281,7 @@ class Ability
 
unless project.builds_enabled
rules += named_abilities('build')
rules += named_abilities('pipeline')
end
 
rules
Loading
Loading
Loading
Loading
@@ -129,6 +129,10 @@ module Ci
!self.commit.latest.include?(self)
end
 
def retry
Ci::Build.retry(self)
end
def depends_on_builds
# Get builds of the same type
latest_builds = self.commit.builds.latest
Loading
Loading
Loading
Loading
@@ -67,6 +67,12 @@ module Ci
.pluck(:stage, :stage_idx).map(&:first)
end
 
def stages
statuses
.group(:stage, :stage_idx).order(:stage_idx)
.pluck(:stage, :stage_idx).map(&:first)
end
def to_param
sha
end
Loading
Loading
@@ -115,6 +121,12 @@ module Ci
Gitlab::Git::branch_ref?(origin_ref)
end
 
def retryable?
builds.latest.any? do |build|
build.failed? || build.retryable?
end
end
def create_builds(ref, tag, user, trigger_request = nil)
return unless config_processor
config_processor.stages.any? do |stage|
Loading
Loading
Loading
Loading
@@ -53,7 +53,7 @@ class CommitStatus < ActiveRecord::Base
scope :failed, -> { where(status: 'failed') }
scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name)) }
scope :ordered, -> { order(:ref, :stage_idx, :name) }
scope :for_ref, ->(ref) { where(ref: ref) }
 
Loading
Loading
Loading
Loading
@@ -3,15 +3,16 @@ module CiStatus
 
module ClassMethods
def status
if all.none?
objs = all.to_a
if objs.none?
nil
elsif all.all? { |status| status.success? || status.ignored? }
elsif objs.all? { |status| status.success? || status.ignored? }
'success'
elsif all.all?(&:pending?)
elsif objs.all?(&:pending?)
'pending'
elsif all.any?(&:running?) || all.any?(&:pending?)
elsif objs.any?(&:running?) || all.any?(&:pending?)
'running'
elsif all.all?(&:canceled?)
elsif objs.all?(&:canceled?)
'canceled'
else
'failed'
Loading
Loading
Loading
Loading
@@ -40,19 +40,18 @@
 
- if project_nav_tab? :builds
= nav_link(controller: %w(ci_commits)) do
= link_to project_ci_commits_path(@project), title: 'CI', class: 'shortcuts-ci' do
= icon('cubes fw')
= link_to project_ci_commits_path(@project), title: 'Pipelines', class: 'shortcuts-builds' do
= icon('ship fw')
%span
CI
Pipelines
%span.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count(:all))
 
- if project_nav_tab? :builds
= nav_link(controller: %w(builds)) do
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
= icon('cubes fw')
%span
Builds
%span.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all))
%span.count.ci_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all))
 
- if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do
Loading
Loading
- status = commit.status
%tr.commit
%td.commit-link
- if can?(current_user, :read_commit, commit)
= link_to namespace_project_commit_url(commit.project.namespace, commit.project, commit) do
%strong ##{commit.id}
- else
= link_to namespace_project_commit_url(@project.namespace, @project, commit), class: "ci-status ci-#{status}" do
= ci_icon_for_status(status)
%strong ##{commit.id}
 
%td.status
%div
- if can?(current_user, :read_commit, commit)
= ci_status_with_icon(commit.status, namespace_project_commit_url(commit.project.namespace, commit.project, commit))
- else
= ci_status_with_icon(commit.status)
%td
%div
- if commit.ref
= link_to commit.ref, namespace_project_commits_path(commit.project.namespace, commit.project, commit.ref)
= link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref)
&nbsp;
- if commit.tag?
%span.label.label-primary tag
Loading
Loading
@@ -35,107 +27,43 @@
%p
Cant find HEAD commit for this branch
 
- stages.each do |stage|
%td
- status = commit.statuses.latest.where(stage: stage).status
- if status
= ci_status_with_icon(status)
- else
= ci_status_with_icon('missing')
%td
%div
Duration:
&nbsp;
- if commit.started_at && commit.finished_at
- if commit.started_at && commit.finished_at
%p
#{duration_in_words(commit.finished_at, commit.started_at)}
- else
\-
- if commit.finished_at
%p
Finished:
&nbsp;
- if commit.finished_at
#{time_ago_with_tooltip(commit.finished_at)}
- else
\-
#{time_ago_with_tooltip(commit.finished_at)}
 
%td.content
.controls.hidden-xs
= link_to project_builds_path(commit.project), class: 'btn btn-grouped btn-xs' do
= icon('cubes fw')
Details
.controls.hidden-xs.pull-right
- artifacts = commit.builds.latest.select { |status| status.artifacts? }
- if artifacts.present?
.dropdown.inline
%button.dropdown-toggle.btn{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
%i.fa.fa-download
%span #{build.name}
&nbsp;
 
- @project = commit.project
- ref = 'master'
%span.btn-group.btn-grouped
%a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
Select Archive Format
%ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.gz
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.bz2
%li
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar
-#%tr.commit
-# %td.status
-# - if can?(current_user, :read_commit, commit)
-# = ci_status_with_icon(commit.status, namespace_project_commit_url(commit.project.namespace, commit.project, commit))
-# - else
-# = ci_status_with_icon(commit.status)
-#
-# %td.commit-link
-# - if can?(current_user, :read_commit, commit)
-# = link_to namespace_project_commit_url(commit.project.namespace, commit.project, commit) do
-# %strong ##{commit.id}
-# - else
-# %strong ##{commit.id}
-#
-# - if defined?(commit_sha) && commit_sha
-# %td
-# = link_to commit.short_sha, namespace_project_commit_path(commit.project.namespace, commit.project, commit.sha), class: "monospace"
-#
-# %td
-# - if commit.ref
-# = link_to commit.ref, namespace_project_commits_path(commit.project.namespace, commit.project, commit.ref)
-# - else
-# .light none
-#
-# %td
-# = commit.git_commit_message
-#
-# %td.duration
-# - if commit.started_at && commit.finished_at
-# #{duration_in_words(commit.finished_at, commit.started_at)}
-#
-# %td.timestamp
-# - if commit.finished_at
-# %span #{time_ago_with_tooltip(commit.finished_at)}
-#
-# - if defined?(coverage) && coverage
-# %td.coverage
-# - if commit.try(:coverage)
-# #{commit.coverage}%
-#
-# %td
-# .pull-right
-# - if can?(current_user, :read_commit, commit) && commit.artifacts?
-# = link_to download_namespace_project_commit_artifacts_path(commit.project.namespace, commit.project, commit), title: 'Download artifacts' do
-# %i.fa.fa-download
-# - if can?(current_user, :update_commit, commit)
-# - if commit.active?
-# = link_to cancel_namespace_project_commit_path(commit.project.namespace, commit.project, commit, return_to: request.original_url), method: :post, title: 'Cancel' do
-# %i.fa.fa-remove.cred
-# - elsif defined?(allow_retry) && allow_retry && commit.retryable?
-# = link_to retry_namespace_project_commit_path(commit.project.namespace, commit.project, commit, return_to: request.original_url), method: :post, title: 'Retry' do
-# %i.fa.fa-repeat
-#
-#- if commit.yaml_errors.present?
-# %tr
-# %td{colspan: 7}
-# .light
-# = commit.yaml_errors
\ No newline at end of file
- if can?(current_user, :update_pipeline, @project)
- if commit.retryable?
= link_to retry_namespace_project_ci_commit_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do
= icon("repeat")
&nbsp;
- if commit.active?
= link_to cancel_namespace_project_ci_commit_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
= icon("remove cred")
- header_title project_title(@project, "CI", project_ci_commits_path(@project))
- header_title project_title(@project, "Pipelines", project_ci_commits_path(@project))
- page_title "CI Changes"
- page_title "Pipelines"
= render "header_title"
 
.top-area
%ul.nav-links
%li{class: ('active' if @scope.nil? || @scope == 'latest')}
= link_to project_ci_commits_path(@project, scope: :latest) do
Latest
%li{class: ('active' if @scope.nil?)}
= link_to project_ci_commits_path(@project) do
All
%span.badge.js-totalbuilds-count
= number_with_delimiter(@all_commits.count(:id))
 
Loading
Loading
@@ -21,31 +21,45 @@
%span.badge.js-running-count
= number_with_delimiter(@all_commits.running_or_pending.count(:id))
 
%li{class: ('active' if @scope == 'failed')}
= link_to project_ci_commits_path(@project, scope: :failed) do
%li{class: ('active' if @scope == 'running')}
= link_to project_ci_commits_path(@project, scope: :running) do
Failed
%span.badge.js-running-count
= number_with_delimiter(@all_commits.finished.count(:id))
= number_with_delimiter(@all_commits.running_or_pending.count(:id))
 
.nav-controls
- if can? current_user, :create_pipeline, @project
= link_to new_namespace_project_ci_commit_path(@project.namespace, @project), class: 'btn btn-create' do
= icon('plus')
New
- if can?(current_user, :update_build, @project)
- unless @repository.gitlab_ci_yml
= link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
= link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
 
= link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench')
%span CI Lint
 
.gray-content-block
#{(@scope || 'running').capitalize} changes on this project
Pipelines for #{(@scope || 'changes')} on this project
 
%ul.content-list
- stages = @commits.stages
- if @commits.blank?
%li
.nothing-here-block No commits to show
.nothing-here-block No pipelines to show
- else
.table-holder
%table.table.builds
= render @commits, commit_sha: true, stage: true, allow_retry: true
%tbody
%th Pipeline ID
%th Commit
- @commits.stages.each do |stage|
%th
= stage.titleize
%th
%th
= render @commits.includes(:statuses).includes(:builds), commit_sha: true, stage: true, allow_retry: true, stages: stages
 
= paginate @commits, theme: 'gitlab'
- page_title "New Pipeline"
= render "header_title"
- if @error
.alert.alert-danger
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
= @error
%h3.page-title
New Pipeline
%hr
= form_tag namespace_project_ci_commits_path, method: :post, id: "new-pipeline-form", class: "form-horizontal js-create-branch-form js-requires-input" do
.form-group
= label_tag :ref, 'Create for', class: 'control-label'
.col-sm-10
= text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
.help-block Existing branch name, tag
.form-actions
= button_tag 'Create pipeline', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', namespace_project_ci_commits_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript
var availableRefs = #{@project.repository.ref_names.to_json};
new NewBranchForm($('.js-create-branch-form'), availableRefs)
- page_title "#{@build.name} (##{@build.id})", "Builds"
= render "header_title"
.build-page
.gray-content-block.top-block
Build ##{@build.id} for commit
%strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
from
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
- merge_request = @build.merge_request
- if merge_request
via
= link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request)
#up-build-trace
- builds = @build.commit.matrix_builds(@build)
- if builds.size > 1
%ul.nav-links.no-top.no-bottom
- builds.each do |build|
%li{class: ('active' if build == @build) }
= link_to namespace_project_build_path(@project.namespace, @project, build) do
= ci_icon_for_status(build.status)
%span
- if build.name
= build.name
- else
= build.id
- if @build.retried?
%li.active
%a
Build ##{@build.id}
&middot;
%i.fa.fa-warning
This build was retried.
.gray-content-block.middle-block
.build-head
.clearfix
= ci_status_with_icon(@build.status)
- if @build.duration
%span
%i.fa.fa-time
#{duration_in_words(@build.finished_at, @build.started_at)}
.pull-right
#{time_ago_with_tooltip(@build.finished_at) if @build.finished_at}
- if @build.stuck?
- unless @build.any_runners_online?
.bs-callout.bs-callout-warning
%p
- if no_runners_for_project?(@build.project)
This build is stuck, because the project doesn't have any runners online assigned to it.
- elsif @build.tags.any?
This build is stuck, because you don't have any active runners online with any of these tags assigned to them:
- @build.tags.each do |tag|
%span.label.label-primary
= tag
- else
This build is stuck, because you don't have any active runners that can run this build.
%br
Go to
= link_to namespace_project_runners_path(@build.project.namespace, @build.project) do
Runners page
.row.prepend-top-default
.col-md-9
.clearfix
- if @build.active?
.autoscroll-container
%button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
.clearfix
#js-build-scroll.scroll-controls
= link_to '#up-build-trace', class: 'btn' do
%i.fa.fa-angle-up
= link_to '#down-build-trace', class: 'btn' do
%i.fa.fa-angle-down
- if @build.erased?
.erased.alert.alert-warning
- erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by
Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)}
- else
%pre.trace#build-trace
%code.bash
= preserve do
= raw @build.trace_html
%div#down-build-trace
.col-md-3
- if @build.coverage
.build-widget
%h4.title
Test coverage
%h1 #{@build.coverage}%
- if can?(current_user, :read_build, @project) && @build.artifacts?
.build-widget.artifacts
%h4.title Build artifacts
.center
.btn-group{ role: :group }
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary' do
= icon('download')
Download
- if @build.artifacts_metadata?
= link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary' do
= icon('folder-open')
Browse
.build-widget
%h4.title
Build ##{@build.id}
- if can?(current_user, :update_build, @project)
.center
.btn-group{ role: :group }
- if @build.active?
= link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-danger', method: :post
- elsif @build.retryable?
= link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary', method: :post
- if @build.erasable?
= link_to erase_namespace_project_build_path(@project.namespace, @project, @build),
class: 'btn btn-sm btn-warning', method: :post,
data: { confirm: 'Are you sure you want to erase this build?' } do
= icon('eraser')
Erase
.clearfix
- if @build.duration
%p
%span.attr-name Duration:
#{duration_in_words(@build.finished_at, @build.started_at)}
%p
%span.attr-name Created:
#{time_ago_with_tooltip(@build.created_at)}
- if @build.finished_at
%p
%span.attr-name Finished:
#{time_ago_with_tooltip(@build.finished_at)}
- if @build.erased_at
%p
%span.attr-name Erased:
#{time_ago_with_tooltip(@build.erased_at)}
%p
%span.attr-name Runner:
- if @build.runner && current_user && current_user.admin
= link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id)
- elsif @build.runner
\##{@build.runner.id}
- if @build.trigger_request
.build-widget
%h4.title
Trigger
%p
%span.attr-name Token:
#{@build.trigger_request.trigger.short_token}
- if @build.trigger_request.variables
%p
%span.attr-name Variables:
%code
- @build.trigger_request.variables.each do |key, value|
#{key}=#{value}
.build-widget
%h4.title
Commit
.pull-right
%small
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
%p
%span.attr-name Branch:
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
%p
%span.attr-name Author:
#{@build.commit.git_author_name}
%p
%span.attr-name Message:
#{@build.commit.git_commit_message}
- if @build.tags.any?
.build-widget
%h4.title
Tags
- @build.tag_list.each do |tag|
%span.label.label-primary
= tag
- if @builds.present?
.build-widget
%h4.title #{pluralize(@builds.count(:id), "other build")} for
= succeed ":" do
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
%table.table.builds
- @builds.each_with_index do |build, i|
%tr.build
%td
= ci_icon_for_status(build.status)
%td
= link_to namespace_project_build_path(@project.namespace, @project, build) do
- if build.name
= build.name
- else
%span ##{build.id}
%td.status= build.status
:javascript
new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}")
Loading
Loading
@@ -653,10 +653,16 @@ Rails.application.routes.draw do
resource :variables, only: [:show, :update]
resources :triggers, only: [:index, :create, :destroy]
 
resources :ci_commits, only: [:index, :show]
resources :ci_commits, only: [:index, :new, :create] do
member do
post :cancel
post :retry
end
end
 
resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
get :commits
post :cancel_all
end
 
Loading
Loading
class AddActionToCiCommit < ActiveRecord::Migration
def change
add_column :ci_commits, :action, :string
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment