Commit c6ebcc91 authored by Yorick Peterse's avatar Yorick Peterse

Merge branch 'rs-verify-sync' into 'master'

Verify sync across remotes before tagging

See merge request !589
parents df0329d7 cb1e326a
......@@ -79,6 +79,8 @@ Metrics/AbcSize:
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 190
Exclude:
- 'lib/release_tools/remote_repository.rb'
# Offense count: 4
Metrics/CyclomaticComplexity:
......
......@@ -53,9 +53,13 @@ module ReleaseTools
end
repository.ensure_branch_exists(stable_branch)
repository.verify_sync!(stable_branch)
bump_versions
push_ref('branch', stable_branch)
push_ref('branch', 'master')
create_tag(tag)
push_ref('tag', tag)
end
......
......@@ -2,6 +2,8 @@
module ReleaseTools
class RemoteRepository
OutOfSyncError = Class.new(StandardError)
class GitCommandError < StandardError
def initialize(message, output = nil)
message += "\n\n #{output.gsub("\n", "\n ")}" unless output.nil?
......@@ -172,6 +174,24 @@ module ReleaseTools
end
end
# Verify the specified ref is the same across all remotes
def verify_sync!(ref)
# TODO: Remove feature flag after testing
return unless ENV['FEATURE_VERIFY_SYNC']
return unless remotes.size > 1
refs = ls_remotes(ref)
return if refs.values.uniq.size == 1
failure_message = refs
.map { |k, v| "#{k}: #{v}" }
.join(', ')
.indent(2)
raise OutOfSyncError, "Remotes are out of sync:\n#{failure_message}"
end
def push(remote, ref)
cmd = %W[push #{remote} #{ref}:#{ref}]
......@@ -228,6 +248,19 @@ module ReleaseTools
status.success?
end
# Returns a Hash of remote => SHA pairs for the specified ref on all remotes
def ls_remotes(ref)
remotes.keys.map do |remote_name|
output, status = run_git(%W[ls-remote #{remote_name} #{ref}])
if status.success?
[remote_name, output.split("\t").first.strip]
else
[remote_name, 'unknown']
end
end.to_h
end
def checkout_branch(branch)
_, status = run_git %W[checkout --quiet #{branch}]
......
......@@ -470,6 +470,43 @@ describe ReleaseTools::RemoteRepository do
end
end
describe '#verify_sync!', :silence_stdout do
around do |ex|
# TODO: Remove feature flag after testing
ClimateControl.modify(FEATURE_VERIFY_SYNC: 'true') do
ex.run
end
end
it 'does nothing with only one remote' do
repo = described_class.get(repo_remotes.slice(:gitlab))
expect(repo).not_to receive(:ls_remotes)
repo.verify_sync!('foo')
end
it 'does nothing when remotes are in sync' do
repo = described_class.get(repo_remotes)
expect(repo).to receive(:ls_remotes).with('foo')
.and_return(gitlab: 'a', security: 'a')
expect { repo.verify_sync!('foo') }
.not_to raise_error
end
it 'raises an error when remotes are out of sync' do
repo = described_class.get(repo_remotes)
expect(repo).to receive(:ls_remotes).with('foo')
.and_return(gitlab: 'a', security: 'b')
expect { repo.verify_sync!('foo') }
.to raise_error(described_class::OutOfSyncError)
end
end
describe '#cleanup', :silence_stdout do
it 'removes the repository path' do
repository = described_class.new(repo_path, {})
......
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