Commit fe03ff90 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'zj-is-anchestor' into 'master'

Ancestor check is Gitaly only now

Closes gitaly#789 and gitaly#308

See merge request gitlab-org/gitlab-ce!20095
parents 246ce5b5 8354ae3c
...@@ -7,82 +7,9 @@ module Gitlab ...@@ -7,82 +7,9 @@ module Gitlab
end end
def update(only_branches_matching: []) def update(only_branches_matching: [])
@repository.gitaly_migrate(:remote_update_remote_mirror) do |is_enabled| @repository.wrapped_gitaly_errors do
if is_enabled
gitaly_update(only_branches_matching)
else
rugged_update(only_branches_matching)
end
end
end
private
def gitaly_update(only_branches_matching)
@repository.gitaly_remote_client.update_remote_mirror(@ref_name, only_branches_matching) @repository.gitaly_remote_client.update_remote_mirror(@ref_name, only_branches_matching)
end end
def rugged_update(only_branches_matching)
local_branches = refs_obj(@repository.local_branches, only_refs_matching: only_branches_matching)
remote_branches = refs_obj(@repository.remote_branches(@ref_name), only_refs_matching: only_branches_matching)
updated_branches = changed_refs(local_branches, remote_branches)
push_branches(updated_branches.keys) if updated_branches.present?
delete_refs(local_branches, remote_branches)
local_tags = refs_obj(@repository.tags)
remote_tags = refs_obj(@repository.remote_tags(@ref_name))
updated_tags = changed_refs(local_tags, remote_tags)
@repository.push_remote_branches(@ref_name, updated_tags.keys) if updated_tags.present?
delete_refs(local_tags, remote_tags)
end
def refs_obj(refs, only_refs_matching: [])
refs.each_with_object({}) do |ref, refs|
next if only_refs_matching.present? && !only_refs_matching.include?(ref.name)
refs[ref.name] = ref
end
end
def changed_refs(local_refs, remote_refs)
local_refs.select do |ref_name, ref|
remote_ref = remote_refs[ref_name]
remote_ref.nil? || ref.dereferenced_target != remote_ref.dereferenced_target
end
end
def push_branches(branches)
default_branch, branches = branches.partition do |branch|
@repository.root_ref == branch
end
# Push the default branch first so it works fine when remote mirror is empty.
branches.unshift(*default_branch)
@repository.push_remote_branches(@ref_name, branches)
end
def delete_refs(local_refs, remote_refs)
refs = refs_to_delete(local_refs, remote_refs)
@repository.delete_remote_branches(@ref_name, refs.keys) if refs.present?
end
def refs_to_delete(local_refs, remote_refs)
default_branch_id = @repository.commit.id
remote_refs.select do |remote_ref_name, remote_ref|
next false if local_refs[remote_ref_name] # skip if branch or tag exist in local repo
remote_ref_id = remote_ref.dereferenced_target.try(:id)
remote_ref_id && @repository.rugged_is_ancestor?(remote_ref_id, default_branch_id)
end
end end
end end
end end
......
...@@ -549,24 +549,9 @@ module Gitlab ...@@ -549,24 +549,9 @@ module Gitlab
end end
end end
# Gitaly note: JV: check gitlab-ee before removing this method.
def rugged_is_ancestor?(ancestor_id, descendant_id)
return false if ancestor_id.nil? || descendant_id.nil?
rugged_merge_base(ancestor_id, descendant_id) == ancestor_id
rescue Rugged::OdbError
false
end
# Returns true is +from+ is direct ancestor to +to+, otherwise false # Returns true is +from+ is direct ancestor to +to+, otherwise false
def ancestor?(from, to) def ancestor?(from, to)
Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
if is_enabled
gitaly_commit_client.ancestor?(from, to) gitaly_commit_client.ancestor?(from, to)
else
rugged_is_ancestor?(from, to)
end
end
end end
def merged_branch_names(branch_names = []) def merged_branch_names(branch_names = [])
......
require 'spec_helper' require 'spec_helper'
describe Projects::UpdateRemoteMirrorService do describe Projects::UpdateRemoteMirrorService do
let(:project) { create(:project, :repository) } set(: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(:repository) { project.repository }
let(:raw_repository) { repository.raw } let(:raw_repository) { repository.raw }
...@@ -9,13 +10,11 @@ describe Projects::UpdateRemoteMirrorService do ...@@ -9,13 +10,11 @@ describe Projects::UpdateRemoteMirrorService do
subject { described_class.new(project, project.creator) } subject { described_class.new(project, project.creator) }
describe "#execute", :skip_gitaly_mock do describe "#execute" do
before do before do
create_branch(repository, 'existing-branch') repository.add_branch(owner, 'existing-branch', 'master')
allow(raw_repository).to receive(:remote_tags) do
generate_tags(repository, 'v1.0.0', 'v1.1.0') allow(remote_mirror).to receive(:update_repository).and_return(true)
end
allow(raw_repository).to receive(:push_remote_branches).and_return(true)
end end
it "fetches the remote repository" do it "fetches the remote repository" do
...@@ -34,46 +33,17 @@ describe Projects::UpdateRemoteMirrorService do ...@@ -34,46 +33,17 @@ describe Projects::UpdateRemoteMirrorService do
expect(result[:status]).to eq(:success) expect(result[:status]).to eq(:success)
end end
describe 'Syncing 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) allow(repository).to receive(:fetch_remote)
expect(raw_repository).to receive(:push_remote_branches).with(remote_mirror.remote_name, local_branch_names) expect(remote_mirror).to receive(:update_repository).with({})
subject.execute(remote_mirror)
end
it "does not push anything is remote is up to date" do
allow(repository).to receive(:fetch_remote) { sync_remote(repository, remote_mirror.remote_name, local_branch_names) }
expect(raw_repository).not_to receive(:push_remote_branches)
subject.execute(remote_mirror)
end
it "sync new branches" do
# call local_branch_names early so it is not called after the new branch has been created
current_branches = local_branch_names
allow(repository).to receive(:fetch_remote) { sync_remote(repository, remote_mirror.remote_name, current_branches) }
create_branch(repository, 'my-new-branch')
expect(raw_repository).to receive(:push_remote_branches).with(remote_mirror.remote_name, ['my-new-branch'])
subject.execute(remote_mirror) subject.execute(remote_mirror)
end end
it "sync updated branches" do
allow(repository).to receive(:fetch_remote) do
sync_remote(repository, remote_mirror.remote_name, local_branch_names)
update_branch(repository, 'existing-branch')
end
expect(raw_repository).to receive(:push_remote_branches).with(remote_mirror.remote_name, ['existing-branch'])
subject.execute(remote_mirror)
end end
context 'when push only protected branches option is set' do context 'when only syncing protected branches' do
let(:unprotected_branch_name) { 'existing-branch' } let(:unprotected_branch_name) { 'existing-branch' }
let(:protected_branch_name) do let(:protected_branch_name) do
project.repository.branch_names.find { |n| n != unprotected_branch_name } project.repository.branch_names.find { |n| n != unprotected_branch_name }
...@@ -88,253 +58,32 @@ describe Projects::UpdateRemoteMirrorService do ...@@ -88,253 +58,32 @@ describe Projects::UpdateRemoteMirrorService do
end end
it "sync updated protected branches" do it "sync updated protected branches" do
allow(repository).to receive(:fetch_remote) do allow(repository).to receive(:fetch_remote)
sync_remote(repository, remote_mirror.remote_name, local_branch_names) expect(remote_mirror).to receive(:update_repository).with(only_branches_matching: [protected_branch_name])
update_branch(repository, protected_branch_name)
end
expect(raw_repository).to receive(:push_remote_branches).with(remote_mirror.remote_name, [protected_branch_name])
subject.execute(remote_mirror)
end
it 'does not sync unprotected branches' do
allow(repository).to receive(:fetch_remote) do
sync_remote(repository, remote_mirror.remote_name, local_branch_names)
update_branch(repository, unprotected_branch_name)
end
expect(raw_repository).not_to receive(:push_remote_branches).with(remote_mirror.remote_name, [unprotected_branch_name])
subject.execute(remote_mirror)
end
end
context 'when branch exists in local and remote repo' do
context 'when it has diverged' do
it 'syncs branches' do
allow(repository).to receive(:fetch_remote) do
sync_remote(repository, remote_mirror.remote_name, local_branch_names)
update_remote_branch(repository, remote_mirror.remote_name, 'markdown')
end
expect(raw_repository).to receive(:push_remote_branches).with(remote_mirror.remote_name, ['markdown'])
subject.execute(remote_mirror)
end
end
end
describe 'for delete' do
context 'when branch exists in local and remote repo' do
it 'deletes the branch from remote repo' do
allow(repository).to receive(:fetch_remote) do
sync_remote(repository, remote_mirror.remote_name, local_branch_names)
delete_branch(repository, 'existing-branch')
end
expect(raw_repository).to receive(:delete_remote_branches).with(remote_mirror.remote_name, ['existing-branch'])
subject.execute(remote_mirror)
end
end
context 'when push only protected branches option is set' do
before do
remote_mirror.only_protected_branches = true
end
context 'when branch exists in local and remote repo' do
let!(:protected_branch_name) { local_branch_names.first }
before do
create(:protected_branch, project: project, name: protected_branch_name)
project.reload
end
it 'deletes the protected branch from remote repo' do
allow(repository).to receive(:fetch_remote) do
sync_remote(repository, remote_mirror.remote_name, local_branch_names)
delete_branch(repository, protected_branch_name)
end
expect(raw_repository).not_to receive(:delete_remote_branches).with(remote_mirror.remote_name, [protected_branch_name])
subject.execute(remote_mirror)
end
it 'does not delete the unprotected branch from remote repo' do
allow(repository).to receive(:fetch_remote) do
sync_remote(repository, remote_mirror.remote_name, local_branch_names)
delete_branch(repository, 'existing-branch')
end
expect(raw_repository).not_to receive(:delete_remote_branches).with(remote_mirror.remote_name, ['existing-branch'])
subject.execute(remote_mirror)
end
end
context 'when branch only exists on remote repo' do
let!(:protected_branch_name) { 'remote-branch' }
before do
create(:protected_branch, project: project, name: protected_branch_name)
end
context 'when it has diverged' do
it 'does not delete the remote branch' do
allow(repository).to receive(:fetch_remote) do
sync_remote(repository, remote_mirror.remote_name, local_branch_names)
rev = repository.find_branch('markdown').dereferenced_target
create_remote_branch(repository, remote_mirror.remote_name, 'remote-branch', rev.id)
end
expect(raw_repository).not_to receive(:delete_remote_branches)
subject.execute(remote_mirror)
end
end
context 'when it has not diverged' do
it 'deletes the remote branch' do
allow(repository).to receive(:fetch_remote) do
sync_remote(repository, remote_mirror.remote_name, local_branch_names)
masterrev = repository.find_branch('master').dereferenced_target
create_remote_branch(repository, remote_mirror.remote_name, protected_branch_name, masterrev.id)
end
expect(raw_repository).to receive(:delete_remote_branches).with(remote_mirror.remote_name, [protected_branch_name])
subject.execute(remote_mirror)
end
end
end
end
context 'when branch only exists on remote repo' do
context 'when it has diverged' do
it 'does not delete the remote branch' do
allow(repository).to receive(:fetch_remote) do
sync_remote(repository, remote_mirror.remote_name, local_branch_names)
rev = repository.find_branch('markdown').dereferenced_target
create_remote_branch(repository, remote_mirror.remote_name, 'remote-branch', rev.id)
end
expect(raw_repository).not_to receive(:delete_remote_branches)
subject.execute(remote_mirror)
end
end
context 'when it has not diverged' do
it 'deletes the remote branch' do
allow(repository).to receive(:fetch_remote) do
sync_remote(repository, remote_mirror.remote_name, local_branch_names)
masterrev = repository.find_branch('master').dereferenced_target
create_remote_branch(repository, remote_mirror.remote_name, 'remote-branch', masterrev.id)
end
expect(raw_repository).to receive(:delete_remote_branches).with(remote_mirror.remote_name, ['remote-branch'])
subject.execute(remote_mirror)
end
end
end
end
end
describe 'Syncing tags' do
before do
allow(repository).to receive(:fetch_remote) { sync_remote(repository, remote_mirror.remote_name, local_branch_names) }
end
context 'when there are not tags to push' do
it 'does not try to push tags' do
allow(repository).to receive(:remote_tags) { {} }
allow(repository).to receive(:tags) { [] }
expect(repository).not_to receive(:push_tags)
subject.execute(remote_mirror)
end
end
context 'when there are some tags to push' do
it 'pushes tags to remote' do
allow(raw_repository).to receive(:remote_tags) { {} }
expect(raw_repository).to receive(:push_remote_branches).with(remote_mirror.remote_name, ['v1.0.0', 'v1.1.0'])
subject.execute(remote_mirror)
end
end
context 'when there are some tags to delete' do
it 'deletes tags from remote' do
remote_tags = generate_tags(repository, 'v1.0.0', 'v1.1.0')
allow(raw_repository).to receive(:remote_tags) { remote_tags }
repository.rm_tag(create(:user), 'v1.0.0')
expect(raw_repository).to receive(:delete_remote_branches).with(remote_mirror.remote_name, ['v1.0.0'])
subject.execute(remote_mirror) subject.execute(remote_mirror)
end end
end end
end end
end
def create_branch(repository, branch_name)
rugged = repository.rugged
masterrev = repository.find_branch('master').dereferenced_target
parentrev = repository.commit(masterrev).parent_id
rugged.references.create("refs/heads/#{branch_name}", parentrev)
repository.expire_branches_cache
end
def create_remote_branch(repository, remote_name, branch_name, source_id)
rugged = repository.rugged
rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", source_id)
end
def sync_remote(repository, remote_name, local_branch_names) def sync_remote(repository, remote_name, local_branch_names)
rugged = repository.rugged
local_branch_names.each do |branch| local_branch_names.each do |branch|
target = repository.find_branch(branch).try(:dereferenced_target) commit = repository.commit(branch)
rugged.references.create("refs/remotes/#{remote_name}/#{branch}", target.id) if target repository.write_ref("refs/remotes/#{remote_name}/#{branch}", commit.id) if commit
end end
end end
def update_remote_branch(repository, remote_name, branch) def update_remote_branch(repository, remote_name, branch)
rugged = repository.rugged masterrev = repository.commit('master').id
masterrev = repository.find_branch('master').dereferenced_target.id
rugged.references.create("refs/remotes/#{remote_name}/#{branch}", masterrev, force: true) repository.write_ref("refs/remotes/#{remote_name}/#{branch}", masterrev, force: true)
repository.expire_branches_cache repository.expire_branches_cache
end end
def update_branch(repository, branch) def update_branch(repository, branch)
rugged = repository.rugged masterrev = repository.commit('master').id
masterrev = repository.find_branch('master').dereferenced_target.id
# Updated existing branch
rugged.references.create("refs/heads/#{branch}", masterrev, force: true)
repository.expire_branches_cache
end
def delete_branch(repository, branch)
rugged = repository.rugged
rugged.references.delete("refs/heads/#{branch}") repository.write_ref("refs/heads/#{branch}", masterrev, force: true)
repository.expire_branches_cache repository.expire_branches_cache
end end
......
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