Commit 405e5fe6 authored by Jonathon Reinhart's avatar Jonathon Reinhart Committed by Douwe Maan

Add support for Git push options, specifically ci.skip

gitlab-org/gitlab-shell!166 added support for collecting push options
from the environment, and passing them along to the
/internal/post_receive API endpoint.

This change handles the new push_options JSON element in the payload,
and passes them on through to the GitPushService and GitTagPushService
services.

Futhermore, it adds support for the first push option, ci.skip.  With
this change, one can use 'git push -o ci.skip' to skip CI pipe
execution. Note that the pipeline is still created, but in the "skipped"
state, just like with the 'ci skip' commit message text.

Implements #18667
parent c9ef9a8a
...@@ -35,6 +35,7 @@ module Ci ...@@ -35,6 +35,7 @@ module Ci
variables_attributes: params[:variables_attributes], variables_attributes: params[:variables_attributes],
project: project, project: project,
current_user: current_user, current_user: current_user,
push_options: params[:push_options],
# EE specific # EE specific
allow_mirror_update: mirror_update, allow_mirror_update: mirror_update,
......
...@@ -180,7 +180,8 @@ class GitPushService < BaseService ...@@ -180,7 +180,8 @@ class GitPushService < BaseService
params[:newrev], params[:newrev],
params[:ref], params[:ref],
@push_commits, @push_commits,
commits_count: commits_count) commits_count: commits_count,
push_options: params[:push_options] || [])
end end
def push_to_existing_branch? def push_to_existing_branch?
......
...@@ -45,7 +45,8 @@ class GitTagPushService < BaseService ...@@ -45,7 +45,8 @@ class GitTagPushService < BaseService
params[:newrev], params[:newrev],
params[:ref], params[:ref],
commits, commits,
message) message,
push_options: params[:push_options] || [])
end end
def build_system_push_data def build_system_push_data
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
class PostReceive class PostReceive
include ApplicationWorker include ApplicationWorker
def perform(gl_repository, identifier, changes)
def perform(gl_repository, identifier, changes, push_options = [])
project, is_wiki = Gitlab::GlRepository.parse(gl_repository) project, is_wiki = Gitlab::GlRepository.parse(gl_repository)
if project.nil? if project.nil?
...@@ -14,7 +15,7 @@ class PostReceive ...@@ -14,7 +15,7 @@ class PostReceive
# Use Sidekiq.logger so arguments can be correlated with execution # Use Sidekiq.logger so arguments can be correlated with execution
# time and thread ID's. # time and thread ID's.
Sidekiq.logger.info "changes: #{changes.inspect}" if ENV['SIDEKIQ_LOG_ARGUMENTS'] Sidekiq.logger.info "changes: #{changes.inspect}" if ENV['SIDEKIQ_LOG_ARGUMENTS']
post_received = Gitlab::GitPostReceive.new(project, identifier, changes) post_received = Gitlab::GitPostReceive.new(project, identifier, changes, push_options)
if is_wiki if is_wiki
process_wiki_changes(post_received) process_wiki_changes(post_received)
...@@ -37,9 +38,21 @@ class PostReceive ...@@ -37,9 +38,21 @@ class PostReceive
post_received.changes_refs do |oldrev, newrev, ref| post_received.changes_refs do |oldrev, newrev, ref|
if Gitlab::Git.tag_ref?(ref) if Gitlab::Git.tag_ref?(ref)
GitTagPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute GitTagPushService.new(
post_received.project,
@user,
oldrev: oldrev,
newrev: newrev,
ref: ref,
push_options: post_received.push_options).execute
elsif Gitlab::Git.branch_ref?(ref) elsif Gitlab::Git.branch_ref?(ref)
GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute GitPushService.new(
post_received.project,
@user,
oldrev: oldrev,
newrev: newrev,
ref: ref,
push_options: post_received.push_options).execute
end end
changes << Gitlab::DataBuilder::Repository.single_change(oldrev, newrev, ref) changes << Gitlab::DataBuilder::Repository.single_change(oldrev, newrev, ref)
......
---
title: Handle ci.skip push option
merge_request: 15643
author: Jonathon Reinhart
type: added
...@@ -2200,6 +2200,12 @@ with an API call. ...@@ -2200,6 +2200,12 @@ with an API call.
If your commit message contains `[ci skip]` or `[skip ci]`, using any If your commit message contains `[ci skip]` or `[skip ci]`, using any
capitalization, the commit will be created but the pipeline will be skipped. capitalization, the commit will be created but the pipeline will be skipped.
Alternatively, one can pass the `ci.skip` [Git push option][push-option] if
using Git 2.10 or newer:
```
$ git push -o ci.skip
```
## Validate the .gitlab-ci.yml ## Validate the .gitlab-ci.yml
Each instance of GitLab CI has an embedded debug tool called Lint, which validates the Each instance of GitLab CI has an embedded debug tool called Lint, which validates the
...@@ -2224,3 +2230,4 @@ GitLab CI/CD with various languages. ...@@ -2224,3 +2230,4 @@ GitLab CI/CD with various languages.
[environment]: ../environments.md "CI/CD environments" [environment]: ../environments.md "CI/CD environments"
[schedules]: ../../user/project/pipelines/schedules.md "Pipelines schedules" [schedules]: ../../user/project/pipelines/schedules.md "Pipelines schedules"
[variables]: ../variables/README.md "CI/CD variables" [variables]: ../variables/README.md "CI/CD variables"
[push-option]: https://git-scm.com/docs/git-push#git-push--oltoptiongt
...@@ -256,8 +256,9 @@ module API ...@@ -256,8 +256,9 @@ module API
post '/post_receive' do post '/post_receive' do
status 200 status 200
PostReceive.perform_async(params[:gl_repository], params[:identifier], PostReceive.perform_async(params[:gl_repository], params[:identifier],
params[:changes]) params[:changes], params[:push_options].to_a)
broadcast_message = BroadcastMessage.current&.last&.message broadcast_message = BroadcastMessage.current&.last&.message
reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease
......
...@@ -10,7 +10,7 @@ module Gitlab ...@@ -10,7 +10,7 @@ module Gitlab
:origin_ref, :checkout_sha, :after_sha, :before_sha, :origin_ref, :checkout_sha, :after_sha, :before_sha,
:trigger_request, :schedule, :merge_request, :trigger_request, :schedule, :merge_request,
:ignore_skip_ci, :save_incompleted, :ignore_skip_ci, :save_incompleted,
:seeds_block, :variables_attributes, :seeds_block, :variables_attributes, :push_options,
# EE specific # EE specific
:allow_mirror_update, :allow_mirror_update,
......
...@@ -8,6 +8,7 @@ module Gitlab ...@@ -8,6 +8,7 @@ module Gitlab
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i
SKIP_PUSH_OPTION = 'ci.skip'
def perform! def perform!
if skipped? if skipped?
...@@ -16,7 +17,7 @@ module Gitlab ...@@ -16,7 +17,7 @@ module Gitlab
end end
def skipped? def skipped?
!@command.ignore_skip_ci && commit_message_skips_ci? !@command.ignore_skip_ci && (commit_message_skips_ci? || push_option_skips_ci?)
end end
def break? def break?
...@@ -32,6 +33,10 @@ module Gitlab ...@@ -32,6 +33,10 @@ module Gitlab
!!(@pipeline.git_commit_message =~ SKIP_PATTERN) !!(@pipeline.git_commit_message =~ SKIP_PATTERN)
end end
end end
def push_option_skips_ci?
!!(@command.push_options&.include?(SKIP_PUSH_OPTION))
end
end end
end end
end end
......
...@@ -31,7 +31,11 @@ module Gitlab ...@@ -31,7 +31,11 @@ module Gitlab
} }
} }
], ],
total_commits_count: 1 total_commits_count: 1,
push_options: [
"ci.skip",
"custom option"
]
}.freeze }.freeze
# Produce a hash of post-receive data # Produce a hash of post-receive data
...@@ -52,10 +56,12 @@ module Gitlab ...@@ -52,10 +56,12 @@ module Gitlab
# homepage: String, # homepage: String,
# }, # },
# commits: Array, # commits: Array,
# total_commits_count: Fixnum # total_commits_count: Fixnum,
# push_options: Array
# } # }
# #
def build(project, user, oldrev, newrev, ref, commits = [], message = nil, commits_count: nil) # rubocop:disable Metrics/ParameterLists
def build(project, user, oldrev, newrev, ref, commits = [], message = nil, commits_count: nil, push_options: [])
commits = Array(commits) commits = Array(commits)
# Total commits count # Total commits count
...@@ -93,6 +99,7 @@ module Gitlab ...@@ -93,6 +99,7 @@ module Gitlab
project: project.hook_attrs, project: project.hook_attrs,
commits: commit_attrs, commits: commit_attrs,
total_commits_count: commits_count, total_commits_count: commits_count,
push_options: push_options,
# DEPRECATED # DEPRECATED
repository: project.hook_attrs.slice(:name, :url, :description, :homepage, repository: project.hook_attrs.slice(:name, :url, :description, :homepage,
:git_http_url, :git_ssh_url, :visibility_level) :git_http_url, :git_ssh_url, :visibility_level)
......
...@@ -3,12 +3,13 @@ ...@@ -3,12 +3,13 @@
module Gitlab module Gitlab
class GitPostReceive class GitPostReceive
include Gitlab::Identifier include Gitlab::Identifier
attr_reader :project, :identifier, :changes attr_reader :project, :identifier, :changes, :push_options
def initialize(project, identifier, changes) def initialize(project, identifier, changes, push_options)
@project = project @project = project
@identifier = identifier @identifier = identifier
@changes = deserialize_changes(changes) @changes = deserialize_changes(changes)
@push_options = push_options
end end
def identify def identify
......
...@@ -809,7 +809,8 @@ describe API::Internal do ...@@ -809,7 +809,8 @@ describe API::Internal do
gl_repository: gl_repository, gl_repository: gl_repository,
secret_token: secret_token, secret_token: secret_token,
identifier: identifier, identifier: identifier,
changes: changes changes: changes,
push_options: push_options
} }
end end
...@@ -817,6 +818,11 @@ describe API::Internal do ...@@ -817,6 +818,11 @@ describe API::Internal do
"#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch"
end end
let(:push_options) do
['ci.skip',
'another push option']
end
before do before do
project.add_developer(user) project.add_developer(user)
allow(described_class).to receive(:identify).and_return(user) allow(described_class).to receive(:identify).and_return(user)
...@@ -825,7 +831,7 @@ describe API::Internal do ...@@ -825,7 +831,7 @@ describe API::Internal do
it 'enqueues a PostReceive worker job' do it 'enqueues a PostReceive worker job' do
expect(PostReceive).to receive(:perform_async) expect(PostReceive).to receive(:perform_async)
.with(gl_repository, identifier, changes) .with(gl_repository, identifier, changes, push_options)
post api("/internal/post_receive"), params: valid_params post api("/internal/post_receive"), params: valid_params
end end
......
...@@ -19,12 +19,14 @@ describe Ci::CreatePipelineService do ...@@ -19,12 +19,14 @@ describe Ci::CreatePipelineService do
ref: ref_name, ref: ref_name,
trigger_request: nil, trigger_request: nil,
variables_attributes: nil, variables_attributes: nil,
merge_request: nil) merge_request: nil,
push_options: nil)
params = { ref: ref, params = { ref: ref,
before: '00000000', before: '00000000',
after: after, after: after,
commits: [{ message: message }], commits: [{ message: message }],
variables_attributes: variables_attributes } variables_attributes: variables_attributes,
push_options: push_options }
described_class.new(project, user, params).execute( described_class.new(project, user, params).execute(
source, trigger_request: trigger_request, merge_request: merge_request) source, trigger_request: trigger_request, merge_request: merge_request)
...@@ -357,6 +359,22 @@ describe Ci::CreatePipelineService do ...@@ -357,6 +359,22 @@ describe Ci::CreatePipelineService do
end end
end end
context 'when push options contain ci.skip' do
let(:push_options) do
['ci.skip',
'another push option']
end
it 'creates a pipline in the skipped state' do
pipeline = execute_service(push_options: push_options)
# TODO: DRY these up with "skips builds creation if the commit message"
expect(pipeline).to be_persisted
expect(pipeline.builds.any?).to be false
expect(pipeline.status).to eq("skipped")
end
end
context 'when there are no jobs for this pipeline' do context 'when there are no jobs for this pipeline' do
before do before do
config = YAML.dump({ test: { script: 'ls', only: ['feature'] } }) config = YAML.dump({ test: { script: 'ls', only: ['feature'] } })
......
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