Synchronize the default branch when updating a remote mirror

parent 7f0a346e
...@@ -2063,6 +2063,12 @@ class Project < ActiveRecord::Base ...@@ -2063,6 +2063,12 @@ class Project < ActiveRecord::Base
auto_cancel_pending_pipelines == 'enabled' auto_cancel_pending_pipelines == 'enabled'
end end
# Update the default branch querying the remote to determine its HEAD
def update_root_ref(remote_name)
root_ref = repository.find_remote_root_ref(remote_name)
change_head(root_ref) if root_ref.present? && root_ref != default_branch
end
private private
def rename_or_migrate_repository! def rename_or_migrate_repository!
......
...@@ -24,6 +24,7 @@ class Repository ...@@ -24,6 +24,7 @@ class Repository
delegate :ref_name_for_sha, to: :raw_repository delegate :ref_name_for_sha, to: :raw_repository
delegate :bundle_to_disk, to: :raw_repository delegate :bundle_to_disk, to: :raw_repository
delegate :find_remote_root_ref, to: :raw_repository
CreateTreeError = Class.new(StandardError) CreateTreeError = Class.new(StandardError)
......
...@@ -5,13 +5,14 @@ module Projects ...@@ -5,13 +5,14 @@ module Projects
attr_reader :errors attr_reader :errors
def execute(remote_mirror) def execute(remote_mirror)
@errors = []
return success unless remote_mirror.enabled? return success unless remote_mirror.enabled?
errors = []
begin begin
remote_mirror.ensure_remote! remote_mirror.ensure_remote!
repository.fetch_remote(remote_mirror.remote_name, no_tags: true) repository.fetch_remote(remote_mirror.remote_name, no_tags: true)
project.update_root_ref(remote_mirror.remote_name)
opts = {} opts = {}
if remote_mirror.only_protected_branches? if remote_mirror.only_protected_branches?
......
...@@ -3983,6 +3983,40 @@ describe Project do ...@@ -3983,6 +3983,40 @@ describe Project do
end end
end end
describe '#update_root_ref' do
let(:project) { create(:project, :repository) }
it 'updates the default branch when HEAD has changed' do
stub_find_remote_root_ref(project, ref: 'feature')
expect { project.update_root_ref('origin') }
.to change { project.default_branch }
.from('master')
.to('feature')
end
it 'does not update the default branch when HEAD does not change' do
stub_find_remote_root_ref(project, ref: 'master')
expect { project.update_root_ref('origin') }
.not_to change { project.default_branch }
end
it 'does not update the default branch when HEAD does not exist' do
stub_find_remote_root_ref(project, ref: 'foo')
expect { project.update_root_ref('origin') }
.not_to change { project.default_branch }
end
def stub_find_remote_root_ref(project, ref:)
allow(project.repository)
.to receive(:find_remote_root_ref)
.with('origin')
.and_return(ref)
end
end
def rugged_config def rugged_config
Gitlab::GitalyClient::StorageSettings.allow_disk_access do Gitlab::GitalyClient::StorageSettings.allow_disk_access do
project.repository.rugged.config project.repository.rugged.config
......
require 'spec_helper' require 'spec_helper'
describe Projects::UpdateRemoteMirrorService do describe Projects::UpdateRemoteMirrorService do
set(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:owner) { project.owner }
let(:remote_project) { create(:forked_project_with_submodules) } let(:remote_project) { create(:forked_project_with_submodules) }
let(:repository) { project.repository }
let(:raw_repository) { repository.raw }
let(:remote_mirror) { project.remote_mirrors.create!(url: remote_project.http_url_to_repo, enabled: true, only_protected_branches: false) } let(:remote_mirror) { project.remote_mirrors.create!(url: remote_project.http_url_to_repo, enabled: true, only_protected_branches: false) }
let(:remote_name) { remote_mirror.remote_name }
subject { described_class.new(project, project.creator) } subject(:service) { described_class.new(project, project.creator) }
describe "#execute" do describe "#execute" do
before do before do
repository.add_branch(owner, 'existing-branch', 'master') project.repository.add_branch(project.owner, 'existing-branch', 'master')
allow(remote_mirror).to receive(:update_repository).and_return(true) allow(remote_mirror).to receive(:update_repository).and_return(true)
end end
it "ensures the remote exists" do
stub_fetch_remote(project, remote_name: remote_name)
stub_find_remote_root_ref(project, remote_name: remote_name)
expect(remote_mirror).to receive(:ensure_remote!)
service.execute(remote_mirror)
end
it "fetches the remote repository" do it "fetches the remote repository" do
expect(remote_mirror).to receive(:ensure_remote!).and_call_original stub_find_remote_root_ref(project, remote_name: remote_name)
expect(repository).to receive(:fetch_remote).with(remote_mirror.remote_name, no_tags: true) do
sync_remote(repository, remote_mirror.remote_name, local_branch_names) expect(project.repository)
.to receive(:fetch_remote)
.with(remote_mirror.remote_name, no_tags: true)
service.execute(remote_mirror)
end end
subject.execute(remote_mirror) it "updates the default branch when HEAD has changed" do
stub_fetch_remote(project, remote_name: remote_name)
stub_find_remote_root_ref(project, remote_name: remote_name, ref: "existing-branch")
expect { service.execute(remote_mirror) }
.to change { project.default_branch }
.from("master")
.to("existing-branch")
end end
it "succeeds" do it "does not update the default branch when HEAD does not change" do
allow(repository).to receive(:fetch_remote) { sync_remote(repository, remote_mirror.remote_name, local_branch_names) } stub_fetch_remote(project, remote_name: remote_name)
stub_find_remote_root_ref(project, remote_name: remote_name, ref: "master")
result = subject.execute(remote_mirror) expect { service.execute(remote_mirror) }.not_to change { project.default_branch }
end
it "returns success when updated succeeds" do
stub_fetch_remote(project, remote_name: remote_name)
stub_find_remote_root_ref(project, remote_name: remote_name)
result = service.execute(remote_mirror)
expect(result[:status]).to eq(:success) expect(result[:status]).to eq(:success)
end end
context 'when syncing all branches' do context 'when syncing all branches' do
it "push all the branches the first time" do it "push all the branches the first time" do
allow(repository).to receive(:fetch_remote) stub_fetch_remote(project, remote_name: remote_name)
stub_find_remote_root_ref(project, remote_name: remote_name)
expect(remote_mirror).to receive(:update_repository).with({}) expect(remote_mirror).to receive(:update_repository).with({})
subject.execute(remote_mirror) service.execute(remote_mirror)
end end
end end
context 'when only syncing protected branches' do context 'when only syncing protected branches' do
let(:unprotected_branch_name) { 'existing-branch' } it "sync updated protected branches" do
let(:protected_branch_name) do stub_fetch_remote(project, remote_name: remote_name)
project.repository.branch_names.find { |n| n != unprotected_branch_name } stub_find_remote_root_ref(project, remote_name: remote_name)
end protected_branch = create_protected_branch(project)
let!(:protected_branch) do
create(:protected_branch, project: project, name: protected_branch_name)
end
before do
project.reload
remote_mirror.only_protected_branches = true remote_mirror.only_protected_branches = true
end
it "sync updated protected branches" do expect(remote_mirror)
allow(repository).to receive(:fetch_remote) .to receive(:update_repository)
expect(remote_mirror).to receive(:update_repository).with(only_branches_matching: [protected_branch_name]) .with(only_branches_matching: [protected_branch.name])
subject.execute(remote_mirror) service.execute(remote_mirror)
end
end
end end
def sync_remote(repository, remote_name, local_branch_names) def create_protected_branch(project)
local_branch_names.each do |branch| branch_name = project.repository.branch_names.find { |n| n != 'existing-branch' }
commit = repository.commit(branch) create(:protected_branch, project: project, name: branch_name)
repository.write_ref("refs/remotes/#{remote_name}/#{branch}", commit.id) if commit
end end
end end
def update_remote_branch(repository, remote_name, branch)
masterrev = repository.commit('master').id
repository.write_ref("refs/remotes/#{remote_name}/#{branch}", masterrev, force: true)
repository.expire_branches_cache
end end
def update_branch(repository, branch) def stub_find_remote_root_ref(project, ref: 'master', remote_name:)
masterrev = repository.commit('master').id allow(project.repository)
.to receive(:find_remote_root_ref)
.with(remote_name)
.and_return(ref)
end
repository.write_ref("refs/heads/#{branch}", masterrev, force: true) def stub_fetch_remote(project, remote_name:)
repository.expire_branches_cache allow(project.repository)
.to receive(:fetch_remote)
.with(remote_name, no_tags: true) { fetch_remote(project.repository, remote_name) }
end end
def generate_tags(repository, *tag_names) def fetch_remote(repository, remote_name)
tag_names.each_with_object([]) do |name, tags| local_branch_names(repository).each do |branch|
tag = repository.find_tag(name) commit = repository.commit(branch)
target = tag.try(:target) repository.write_ref("refs/remotes/#{remote_name}/#{branch}", commit.id) if commit
target_commit = tag.try(:dereferenced_target)
tags << Gitlab::Git::Tag.new(repository.raw_repository, {
name: name,
target: target,
target_commit: target_commit
})
end end
end end
def local_branch_names def local_branch_names(repository)
branch_names = repository.branches.map(&:name) branch_names = repository.branches.map(&:name)
# we want the protected branch to be pushed first # we want the protected branch to be pushed first
branch_names.unshift(branch_names.delete('master')) branch_names.unshift(branch_names.delete('master'))
......
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