2016-10-24 15:19:46 -04:00
|
|
|
|
# rubocop: disable Rails/Output
|
2016-10-17 12:52:14 -04:00
|
|
|
|
module Gitlab
|
|
|
|
|
# Checks if a set of migrations requires downtime or not.
|
|
|
|
|
class EeCompatCheck
|
2016-10-28 12:13:59 -04:00
|
|
|
|
CE_REPO = 'https://gitlab.com/gitlab-org/gitlab-ce.git'.freeze
|
2016-10-17 12:52:14 -04:00
|
|
|
|
EE_REPO = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
|
2016-10-28 12:13:59 -04:00
|
|
|
|
CHECK_DIR = Rails.root.join('ee_compat_check')
|
|
|
|
|
MAX_FETCH_DEPTH = 500
|
|
|
|
|
IGNORED_FILES_REGEX = /(VERSION|CHANGELOG\.md:\d+)/.freeze
|
2016-10-17 12:52:14 -04:00
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
attr_reader :repo_dir, :patches_dir, :ce_repo, :ce_branch
|
2016-10-17 12:52:14 -04:00
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
def initialize(branch:, ce_repo: CE_REPO)
|
|
|
|
|
@repo_dir = CHECK_DIR.join('repo')
|
|
|
|
|
@patches_dir = CHECK_DIR.join('patches')
|
2016-10-17 12:52:14 -04:00
|
|
|
|
@ce_branch = branch
|
2016-10-28 12:13:59 -04:00
|
|
|
|
@ce_repo = ce_repo
|
2016-10-17 12:52:14 -04:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def check
|
|
|
|
|
ensure_ee_repo
|
2016-10-28 12:13:59 -04:00
|
|
|
|
ensure_patches_dir
|
2016-10-17 12:52:14 -04:00
|
|
|
|
|
|
|
|
|
generate_patch(ce_branch, ce_patch_full_path)
|
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
Dir.chdir(repo_dir) do
|
|
|
|
|
step("In the #{repo_dir} directory")
|
2016-10-17 12:52:14 -04:00
|
|
|
|
|
|
|
|
|
status = catch(:halt_check) do
|
|
|
|
|
ce_branch_compat_check!
|
2016-10-28 12:13:59 -04:00
|
|
|
|
delete_ee_branch_locally!
|
2016-10-17 12:52:14 -04:00
|
|
|
|
ee_branch_presence_check!
|
|
|
|
|
ee_branch_compat_check!
|
|
|
|
|
end
|
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
delete_ee_branch_locally!
|
2016-10-17 12:52:14 -04:00
|
|
|
|
|
|
|
|
|
if status.nil?
|
|
|
|
|
true
|
|
|
|
|
else
|
|
|
|
|
false
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
|
|
def ensure_ee_repo
|
2016-10-28 12:13:59 -04:00
|
|
|
|
if Dir.exist?(repo_dir)
|
|
|
|
|
step("#{repo_dir} already exists")
|
2016-10-17 12:52:14 -04:00
|
|
|
|
else
|
2016-10-28 12:13:59 -04:00
|
|
|
|
cmd = %W[git clone --branch master --single-branch --depth 200 #{EE_REPO} #{repo_dir}]
|
|
|
|
|
step("Cloning #{EE_REPO} into #{repo_dir}", cmd)
|
2016-10-17 12:52:14 -04:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
def ensure_patches_dir
|
|
|
|
|
FileUtils.mkdir_p(patches_dir)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def generate_patch(branch, patch_path)
|
|
|
|
|
FileUtils.rm(patch_path, force: true)
|
|
|
|
|
|
|
|
|
|
depth = 0
|
|
|
|
|
loop do
|
|
|
|
|
depth += 50
|
|
|
|
|
cmd = %W[git fetch --depth #{depth} origin --prune +refs/heads/master:refs/remotes/origin/master]
|
|
|
|
|
Gitlab::Popen.popen(cmd)
|
|
|
|
|
_, status = Gitlab::Popen.popen(%w[git merge-base FETCH_HEAD HEAD])
|
|
|
|
|
|
|
|
|
|
raise "#{branch} is too far behind master, please rebase it!" if depth >= MAX_FETCH_DEPTH
|
|
|
|
|
break if status.zero?
|
|
|
|
|
end
|
2016-10-17 12:52:14 -04:00
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
step("Generating the patch against master in #{patch_path}")
|
|
|
|
|
output, status = Gitlab::Popen.popen(%w[git format-patch FETCH_HEAD --stdout])
|
|
|
|
|
throw(:halt_check, :ko) unless status.zero?
|
|
|
|
|
|
|
|
|
|
File.write(patch_path, output)
|
|
|
|
|
throw(:halt_check, :ko) unless File.exist?(patch_path)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def ce_branch_compat_check!
|
|
|
|
|
if check_patch(ce_patch_full_path).zero?
|
|
|
|
|
puts applies_cleanly_msg(ce_branch)
|
2016-10-17 12:52:14 -04:00
|
|
|
|
throw(:halt_check)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def ee_branch_presence_check!
|
|
|
|
|
status = step("Fetching origin/#{ee_branch}", %W[git fetch origin #{ee_branch}])
|
|
|
|
|
|
|
|
|
|
unless status.zero?
|
|
|
|
|
puts
|
|
|
|
|
puts ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg
|
|
|
|
|
|
|
|
|
|
throw(:halt_check, :ko)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def ee_branch_compat_check!
|
|
|
|
|
step("Checking out origin/#{ee_branch}", %W[git checkout -b #{ee_branch} FETCH_HEAD])
|
|
|
|
|
|
|
|
|
|
generate_patch(ee_branch, ee_patch_full_path)
|
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
unless check_patch(ee_patch_full_path).zero?
|
2016-10-17 12:52:14 -04:00
|
|
|
|
puts
|
|
|
|
|
puts ee_branch_doesnt_apply_cleanly_msg
|
|
|
|
|
|
|
|
|
|
throw(:halt_check, :ko)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
puts
|
2016-10-28 12:13:59 -04:00
|
|
|
|
puts applies_cleanly_msg(ee_branch)
|
2016-10-17 12:52:14 -04:00
|
|
|
|
end
|
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
def check_patch(patch_path)
|
|
|
|
|
step("Checking out master", %w[git checkout master])
|
|
|
|
|
step("Reseting to latest master", %w[git reset --hard origin/master])
|
2016-10-17 12:52:14 -04:00
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
step("Checking if #{patch_path} applies cleanly to EE/master")
|
|
|
|
|
output, status = Gitlab::Popen.popen(%W[git apply --check #{patch_path}])
|
2016-10-17 12:52:14 -04:00
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
unless status.zero?
|
|
|
|
|
failed_files = output.lines.reduce([]) do |memo, line|
|
|
|
|
|
if line.start_with?('error: patch failed:')
|
|
|
|
|
file = line.sub(/\Aerror: patch failed: /, '')
|
|
|
|
|
memo << file unless file =~ IGNORED_FILES_REGEX
|
|
|
|
|
end
|
|
|
|
|
memo
|
|
|
|
|
end
|
2016-10-17 12:52:14 -04:00
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
if failed_files.empty?
|
|
|
|
|
status = 0
|
|
|
|
|
else
|
|
|
|
|
puts "\nConflicting files:"
|
|
|
|
|
failed_files.each do |file|
|
|
|
|
|
puts " - #{file}"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2016-10-17 12:52:14 -04:00
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
status
|
2016-10-17 12:52:14 -04:00
|
|
|
|
end
|
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
def delete_ee_branch_locally!
|
2016-10-24 14:29:00 -04:00
|
|
|
|
command(%w[git checkout master])
|
2016-10-17 12:52:14 -04:00
|
|
|
|
step("Deleting the local #{ee_branch} branch", %W[git branch -D #{ee_branch}])
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def ce_patch_name
|
2016-11-22 13:13:41 -05:00
|
|
|
|
@ce_patch_name ||= patch_name_from_branch(ce_branch)
|
2016-10-17 12:52:14 -04:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def ce_patch_full_path
|
2016-10-28 12:13:59 -04:00
|
|
|
|
@ce_patch_full_path ||= patches_dir.join(ce_patch_name)
|
2016-10-17 12:52:14 -04:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def ee_branch
|
|
|
|
|
@ee_branch ||= "#{ce_branch}-ee"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def ee_patch_name
|
2016-11-22 13:13:41 -05:00
|
|
|
|
@ee_patch_name ||= patch_name_from_branch(ee_branch)
|
2016-10-17 12:52:14 -04:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def ee_patch_full_path
|
2016-10-28 12:13:59 -04:00
|
|
|
|
@ee_patch_full_path ||= patches_dir.join(ee_patch_name)
|
2016-10-17 12:52:14 -04:00
|
|
|
|
end
|
|
|
|
|
|
2016-11-22 13:13:41 -05:00
|
|
|
|
def patch_name_from_branch(branch_name)
|
|
|
|
|
branch_name.parameterize << '.patch'
|
|
|
|
|
end
|
|
|
|
|
|
2016-10-17 12:52:14 -04:00
|
|
|
|
def step(desc, cmd = nil)
|
|
|
|
|
puts "\n=> #{desc}\n"
|
|
|
|
|
|
|
|
|
|
if cmd
|
2016-10-28 12:13:59 -04:00
|
|
|
|
start = Time.now
|
2016-10-17 12:52:14 -04:00
|
|
|
|
puts "\n$ #{cmd.join(' ')}"
|
2016-10-28 12:13:59 -04:00
|
|
|
|
status = command(cmd)
|
|
|
|
|
puts "\nFinished in #{Time.now - start} seconds"
|
|
|
|
|
status
|
2016-10-17 12:52:14 -04:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def command(cmd)
|
|
|
|
|
output, status = Gitlab::Popen.popen(cmd)
|
|
|
|
|
puts output
|
|
|
|
|
|
|
|
|
|
status
|
|
|
|
|
end
|
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
def applies_cleanly_msg(branch)
|
2016-10-17 12:52:14 -04:00
|
|
|
|
<<-MSG.strip_heredoc
|
|
|
|
|
=================================================================
|
|
|
|
|
🎉 Congratulations!! 🎉
|
|
|
|
|
|
2016-10-28 12:13:59 -04:00
|
|
|
|
The #{branch} branch applies cleanly to EE/master!
|
2016-10-17 12:52:14 -04:00
|
|
|
|
|
|
|
|
|
Much ❤️!!
|
|
|
|
|
=================================================================\n
|
|
|
|
|
MSG
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg
|
|
|
|
|
<<-MSG.strip_heredoc
|
|
|
|
|
=================================================================
|
|
|
|
|
💥 Oh no! 💥
|
|
|
|
|
|
|
|
|
|
The #{ce_branch} branch does not apply cleanly to the current
|
|
|
|
|
EE/master, and no #{ee_branch} branch was found in the EE repository.
|
|
|
|
|
|
|
|
|
|
Please create a #{ee_branch} branch that includes changes from
|
|
|
|
|
#{ce_branch} but also specific changes than can be applied cleanly
|
|
|
|
|
to EE/master.
|
|
|
|
|
|
|
|
|
|
There are different ways to create such branch:
|
|
|
|
|
|
|
|
|
|
1. Create a new branch based on the CE branch and rebase it on top of EE/master
|
|
|
|
|
|
|
|
|
|
# In the EE repo
|
|
|
|
|
$ git fetch #{ce_repo} #{ce_branch}
|
|
|
|
|
$ git checkout -b #{ee_branch} FETCH_HEAD
|
|
|
|
|
|
|
|
|
|
# You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit
|
|
|
|
|
# before rebasing to limit the conflicts-resolving steps during the rebase
|
|
|
|
|
$ git fetch origin
|
|
|
|
|
$ git rebase origin/master
|
|
|
|
|
|
|
|
|
|
At this point you will likely have conflicts.
|
|
|
|
|
Solve them, and continue/finish the rebase.
|
|
|
|
|
|
|
|
|
|
You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE".
|
|
|
|
|
|
|
|
|
|
2. Create a new branch from master and cherry-pick your CE commits
|
|
|
|
|
|
|
|
|
|
# In the EE repo
|
|
|
|
|
$ git fetch origin
|
2016-10-28 12:13:59 -04:00
|
|
|
|
$ git checkout -b #{ee_branch} origin/master
|
2016-10-17 12:52:14 -04:00
|
|
|
|
$ git fetch #{ce_repo} #{ce_branch}
|
|
|
|
|
$ git cherry-pick SHA # Repeat for all the commits you want to pick
|
|
|
|
|
|
|
|
|
|
You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit.
|
|
|
|
|
|
|
|
|
|
Don't forget to push your branch to #{EE_REPO}:
|
|
|
|
|
|
|
|
|
|
# In the EE repo
|
|
|
|
|
$ git push origin #{ee_branch}
|
|
|
|
|
|
|
|
|
|
You can then retry this failed build, and hopefully it should pass.
|
|
|
|
|
|
|
|
|
|
Stay 💪 !
|
|
|
|
|
=================================================================\n
|
|
|
|
|
MSG
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def ee_branch_doesnt_apply_cleanly_msg
|
|
|
|
|
<<-MSG.strip_heredoc
|
|
|
|
|
=================================================================
|
|
|
|
|
💥 Oh no! 💥
|
|
|
|
|
|
|
|
|
|
The #{ce_branch} does not apply cleanly to the current
|
|
|
|
|
EE/master, and even though a #{ee_branch} branch exists in the EE
|
|
|
|
|
repository, it does not apply cleanly either to EE/master!
|
|
|
|
|
|
|
|
|
|
Please update the #{ee_branch}, push it again to #{EE_REPO}, and
|
|
|
|
|
retry this build.
|
|
|
|
|
|
|
|
|
|
Stay 💪 !
|
|
|
|
|
=================================================================\n
|
|
|
|
|
MSG
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|