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
variables_attributes: params[:variables_attributes],
project: project,
current_user: current_user,
push_options: params[:push_options],
# EE specific
allow_mirror_update: mirror_update,
......
......@@ -180,7 +180,8 @@ class GitPushService < BaseService
params[:newrev],
params[:ref],
@push_commits,
commits_count: commits_count)
commits_count: commits_count,
push_options: params[:push_options] || [])
end
def push_to_existing_branch?
......
......@@ -45,7 +45,8 @@ class GitTagPushService < BaseService
params[:newrev],
params[:ref],
commits,
message)
message,
push_options: params[:push_options] || [])
end
def build_system_push_data
......
......@@ -2,7 +2,8 @@
class PostReceive
include ApplicationWorker
def perform(gl_repository, identifier, changes)
def perform(gl_repository, identifier, changes, push_options = [])
project, is_wiki = Gitlab::GlRepository.parse(gl_repository)
if project.nil?
......@@ -14,7 +15,7 @@ class PostReceive
# Use Sidekiq.logger so arguments can be correlated with execution
# time and thread ID's.
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
process_wiki_changes(post_received)
......@@ -37,9 +38,21 @@ class PostReceive
post_received.changes_refs do |oldrev, newrev, 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)
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
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.
If your commit message contains `[ci skip]` or `[skip ci]`, using any
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
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.
[environment]: ../environments.md "CI/CD environments"
[schedules]: ../../user/project/pipelines/schedules.md "Pipelines schedules"
[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
post '/post_receive' do
status 200
PostReceive.perform_async(params[:gl_repository], params[:identifier],
params[:changes])
params[:changes], params[:push_options].to_a)
broadcast_message = BroadcastMessage.current&.last&.message
reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease
......
......@@ -10,7 +10,7 @@ module Gitlab
:origin_ref, :checkout_sha, :after_sha, :before_sha,
:trigger_request, :schedule, :merge_request,
:ignore_skip_ci, :save_incompleted,
:seeds_block, :variables_attributes,
:seeds_block, :variables_attributes, :push_options,
# EE specific
:allow_mirror_update,
......
......@@ -8,6 +8,7 @@ module Gitlab
include ::Gitlab::Utils::StrongMemoize
SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i
SKIP_PUSH_OPTION = 'ci.skip'
def perform!
if skipped?
......@@ -16,7 +17,7 @@ module Gitlab
end
def skipped?
!@command.ignore_skip_ci && commit_message_skips_ci?
!@command.ignore_skip_ci && (commit_message_skips_ci? || push_option_skips_ci?)
end
def break?
......@@ -32,6 +33,10 @@ module Gitlab
!!(@pipeline.git_commit_message =~ SKIP_PATTERN)
end
end
def push_option_skips_ci?
!!(@command.push_options&.include?(SKIP_PUSH_OPTION))
end
end
end
end
......
......@@ -31,7 +31,11 @@ module Gitlab
}
}
],
total_commits_count: 1
total_commits_count: 1,
push_options: [
"ci.skip",
"custom option"
]
}.freeze
# Produce a hash of post-receive data
......@@ -52,10 +56,12 @@ module Gitlab
# homepage: String,
# },
# 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)
# Total commits count
......@@ -93,6 +99,7 @@ module Gitlab
project: project.hook_attrs,
commits: commit_attrs,
total_commits_count: commits_count,
push_options: push_options,
# DEPRECATED
repository: project.hook_attrs.slice(:name, :url, :description, :homepage,
:git_http_url, :git_ssh_url, :visibility_level)
......
......@@ -3,12 +3,13 @@
module Gitlab
class GitPostReceive
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
@identifier = identifier
@changes = deserialize_changes(changes)
@push_options = push_options
end
def identify
......
......@@ -809,7 +809,8 @@ describe API::Internal do
gl_repository: gl_repository,
secret_token: secret_token,
identifier: identifier,
changes: changes
changes: changes,
push_options: push_options
}
end
......@@ -817,6 +818,11 @@ describe API::Internal do
"#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch"
end
let(:push_options) do
['ci.skip',
'another push option']
end
before do
project.add_developer(user)
allow(described_class).to receive(:identify).and_return(user)
......@@ -825,7 +831,7 @@ describe API::Internal do
it 'enqueues a PostReceive worker job' do
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
end
......
......@@ -19,12 +19,14 @@ describe Ci::CreatePipelineService do
ref: ref_name,
trigger_request: nil,
variables_attributes: nil,
merge_request: nil)
merge_request: nil,
push_options: nil)
params = { ref: ref,
before: '00000000',
after: after,
commits: [{ message: message }],
variables_attributes: variables_attributes }
variables_attributes: variables_attributes,
push_options: push_options }
described_class.new(project, user, params).execute(
source, trigger_request: trigger_request, merge_request: merge_request)
......@@ -357,6 +359,22 @@ describe Ci::CreatePipelineService do
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
before do
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