mirror of
https://gitlab.com/gitlab-org/gitlab-foss.git
synced 2025-08-06 10:19:48 +00:00
231 lines
7.7 KiB
Ruby
231 lines
7.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'active_support'
|
|
require 'active_support/core_ext'
|
|
require 'active_support/core_ext/string'
|
|
require 'gitlab/housekeeper/logger'
|
|
require 'gitlab/housekeeper/keep'
|
|
require 'gitlab/housekeeper/gitlab_client'
|
|
require 'gitlab/housekeeper/git'
|
|
require 'gitlab/housekeeper/change'
|
|
require 'gitlab/housekeeper/substitutor'
|
|
require 'gitlab/housekeeper/filter_identifiers'
|
|
require 'awesome_print'
|
|
require 'digest'
|
|
|
|
module Gitlab
|
|
module Housekeeper
|
|
class Runner
|
|
def initialize(
|
|
max_mrs: 1,
|
|
dry_run: false,
|
|
keeps: nil,
|
|
filter_identifiers: [],
|
|
push_when_approved: false,
|
|
target_branch: 'master')
|
|
@max_mrs = max_mrs
|
|
@dry_run = dry_run
|
|
@logger = Logger.new($stdout)
|
|
@target_branch = target_branch
|
|
@push_when_approved = push_when_approved
|
|
require_keeps
|
|
|
|
@keeps = if keeps
|
|
keeps.map { |k| k.is_a?(String) ? k.constantize : k }
|
|
else
|
|
all_keeps
|
|
end
|
|
|
|
@filter_identifiers = ::Gitlab::Housekeeper::FilterIdentifiers.new(filter_identifiers)
|
|
end
|
|
|
|
def run
|
|
mrs_created_count = 0
|
|
|
|
git.with_clean_state do
|
|
@keeps.each do |keep_class|
|
|
@logger.puts "Running keep #{keep_class}"
|
|
keep = keep_class.new(logger: @logger, filter_identifiers: @filter_identifiers)
|
|
keep.each_change do |change|
|
|
unless change.valid?
|
|
@logger.warn "Ignoring invalid change from #{keep_class} with identifier #{change.identifiers}"
|
|
next
|
|
end
|
|
|
|
change.keep_class ||= keep_class
|
|
branch_name = git.create_branch(change)
|
|
add_standard_change_data(change)
|
|
|
|
next if skip_change_if_necessary(change, branch_name)
|
|
|
|
setup_merge_request(change, branch_name) unless @dry_run
|
|
|
|
git.in_branch(branch_name) do
|
|
Gitlab::Housekeeper::Substitutor.perform(change)
|
|
git.create_commit(change)
|
|
end
|
|
|
|
print_change_details(change, branch_name)
|
|
create(change, branch_name) unless @dry_run
|
|
|
|
mrs_created_count += 1
|
|
break if mrs_created_count >= @max_mrs
|
|
end
|
|
break if mrs_created_count >= @max_mrs
|
|
end
|
|
end
|
|
|
|
print_completion_message(mrs_created_count)
|
|
end
|
|
|
|
def print_completion_message(mrs_created_count)
|
|
mr_count_string = "#{mrs_created_count} #{'MR'.pluralize(mrs_created_count)}"
|
|
|
|
completion_message = if @dry_run
|
|
"Dry run complete. Housekeeper would have created #{mr_count_string} on an actual run."
|
|
else
|
|
"Housekeeper created #{mr_count_string}."
|
|
end
|
|
|
|
@logger.puts completion_message.yellowish
|
|
@logger.puts
|
|
end
|
|
|
|
def add_standard_change_data(change)
|
|
change.labels ||= []
|
|
change.labels << 'automation:gitlab-housekeeper-authored'
|
|
end
|
|
|
|
def skip_change_if_necessary(change, branch_name)
|
|
if change.aborted? || !@filter_identifiers.matches_filters?(change.identifiers) ||
|
|
(!@dry_run && has_closed_merge_request?(branch_name))
|
|
git.in_branch(branch_name) do
|
|
git.create_commit(change)
|
|
end
|
|
|
|
if change.aborted?
|
|
@logger.puts "Skipping change as it is marked aborted."
|
|
elsif !@filter_identifiers.matches_filters?(change.identifiers)
|
|
@logger.puts "Skipping change: #{change.identifiers} due to not matching filter."
|
|
else
|
|
@logger.puts "Skipping change as we have closed an MR for this branch #{branch_name}"
|
|
end
|
|
|
|
@logger.puts "Modified files have been committed to branch #{branch_name.yellowish}, " \
|
|
"but will not be pushed."
|
|
@logger.puts
|
|
return true
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
def setup_merge_request(change, branch_name)
|
|
merge_request = get_existing_merge_request(branch_name) || create(change, branch_name)
|
|
change.mr_web_url = merge_request['web_url']
|
|
end
|
|
|
|
def git
|
|
@git ||= ::Gitlab::Housekeeper::Git.new(logger: @logger, branch_from: @target_branch)
|
|
end
|
|
|
|
def require_keeps
|
|
Dir.glob("keeps/*.rb").each do |f|
|
|
require(Pathname(f).expand_path.to_s)
|
|
end
|
|
end
|
|
|
|
def print_change_details(change, branch_name)
|
|
base_message = "Merge request URL: #{change.mr_web_url || '(known after create)'}, on branch #{branch_name}. " \
|
|
"Squash commits enabled."
|
|
base_message << " CI skipped." if change.push_options.ci_skip
|
|
|
|
@logger.puts base_message.yellowish
|
|
@logger.puts "=> #{change.identifiers.join(': ')}".purple
|
|
|
|
@logger.puts '=> Title:'.purple
|
|
@logger.puts change.title.purple
|
|
@logger.puts
|
|
|
|
@logger.puts '=> Description:'
|
|
@logger.puts change.mr_description
|
|
@logger.puts
|
|
|
|
if change.labels.present? || change.assignees.present? || change.reviewers.present?
|
|
@logger.puts '=> Attributes:'
|
|
@logger.puts "Labels: #{change.labels.join(', ')}"
|
|
@logger.puts "Assignees: #{change.assignees.join(', ')}"
|
|
@logger.puts "Reviewers: #{change.reviewers.join(', ')}"
|
|
@logger.puts
|
|
end
|
|
|
|
@logger.puts '=> Diff:'
|
|
@logger.puts Shell.execute('git', '--no-pager', 'diff', '--color=always', @target_branch, branch_name, '--',
|
|
*change.changed_files)
|
|
@logger.puts
|
|
end
|
|
|
|
def create(change, branch_name)
|
|
change.non_housekeeper_changes = gitlab_client.non_housekeeper_changes(
|
|
source_project_id: housekeeper_fork_project_id,
|
|
source_branch: branch_name,
|
|
target_branch: @target_branch,
|
|
target_project_id: housekeeper_target_project_id
|
|
)
|
|
|
|
git.push(branch_name, change.push_options) if self.class.should_push_code?(change, @push_when_approved)
|
|
|
|
gitlab_client.create_or_update_merge_request(
|
|
change: change,
|
|
source_project_id: housekeeper_fork_project_id,
|
|
source_branch: branch_name,
|
|
target_branch: @target_branch,
|
|
target_project_id: housekeeper_target_project_id
|
|
)
|
|
end
|
|
|
|
def get_existing_merge_request(branch_name)
|
|
gitlab_client.get_existing_merge_request(
|
|
source_project_id: housekeeper_fork_project_id,
|
|
source_branch: branch_name,
|
|
target_branch: @target_branch,
|
|
target_project_id: housekeeper_target_project_id
|
|
)
|
|
end
|
|
|
|
def has_closed_merge_request?(branch_name)
|
|
gitlab_client.closed_merge_request_exists?(
|
|
source_project_id: housekeeper_fork_project_id,
|
|
source_branch: branch_name,
|
|
target_branch: @target_branch,
|
|
target_project_id: housekeeper_target_project_id
|
|
)
|
|
end
|
|
|
|
# We do not want to push code if the MR already has approvals as it will reset the approvals. Also we do not push
|
|
# if someone else has added commits already.
|
|
def self.should_push_code?(change, push_when_approved)
|
|
return false if change.already_approved? && !push_when_approved
|
|
|
|
change.update_required?(:code)
|
|
end
|
|
|
|
def housekeeper_fork_project_id
|
|
ENV.fetch('HOUSEKEEPER_FORK_PROJECT_ID', housekeeper_target_project_id)
|
|
end
|
|
|
|
def housekeeper_target_project_id
|
|
ENV.fetch('HOUSEKEEPER_TARGET_PROJECT_ID')
|
|
end
|
|
|
|
def gitlab_client
|
|
@gitlab_client ||= GitlabClient.new
|
|
end
|
|
|
|
def all_keeps
|
|
@all_keeps ||= ObjectSpace.each_object(Class).select { |klass| klass < Keep }
|
|
end
|
|
end
|
|
end
|
|
end
|