Commit 95a3e2da authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'fix/import-group-members' into 'master'

Fix missing group members from Import/Export

Closes #25124

See merge request !8923
parents f88672b0 8409340b
...@@ -26,7 +26,7 @@ module Projects ...@@ -26,7 +26,7 @@ module Projects
end end
def project_tree_saver def project_tree_saver
Gitlab::ImportExport::ProjectTreeSaver.new(project: project, shared: @shared) Gitlab::ImportExport::ProjectTreeSaver.new(project: project, current_user: @current_user, shared: @shared)
end end
def uploads_saver def uploads_saver
......
---
title: Add ability to export project inherited group members to Import/Export
merge_request: 8923
author:
...@@ -14,6 +14,11 @@ ...@@ -14,6 +14,11 @@
> raketask. > raketask.
> - The exports are stored in a temporary [shared directory][tmp] and are deleted > - The exports are stored in a temporary [shared directory][tmp] and are deleted
> every 24 hours by a specific worker. > every 24 hours by a specific worker.
> - Group members will get exported as project members, as long as the user has
> master or admin access to the group where the exported project lives. An admin
> in the import side is required to map the users, based on email or username.
> Otherwise, a supplementary comment is left to mention the original author and
> the MRs, notes or issues will be owned by the importer.
Existing projects running on any GitLab instance or GitLab.com can be exported Existing projects running on any GitLab instance or GitLab.com can be exported
with all their related data and be moved into a new GitLab instance. with all their related data and be moved into a new GitLab instance.
...@@ -22,7 +27,7 @@ with all their related data and be moved into a new GitLab instance. ...@@ -22,7 +27,7 @@ with all their related data and be moved into a new GitLab instance.
| GitLab version | Import/Export version | | GitLab version | Import/Export version |
| -------- | -------- | | -------- | -------- |
| 8.16.2 to current | 0.1.6 | | 8.17.0 to current | 0.1.6 |
| 8.13.0 | 0.1.5 | | 8.13.0 | 0.1.5 |
| 8.12.0 | 0.1.4 | | 8.12.0 | 0.1.4 |
| 8.10.3 | 0.1.3 | | 8.10.3 | 0.1.3 |
......
...@@ -32,6 +32,10 @@ module Gitlab ...@@ -32,6 +32,10 @@ module Gitlab
@user.id @user.id
end end
def include?(old_author_id)
map.keys.include?(old_author_id) && map[old_author_id] != default_user_id
end
private private
def missing_keys_tracking_hash def missing_keys_tracking_hash
......
...@@ -5,8 +5,9 @@ module Gitlab ...@@ -5,8 +5,9 @@ module Gitlab
attr_reader :full_path attr_reader :full_path
def initialize(project:, shared:) def initialize(project:, current_user:, shared:)
@project = project @project = project
@current_user = current_user
@shared = shared @shared = shared
@full_path = File.join(@shared.export_path, ImportExport.project_filename) @full_path = File.join(@shared.export_path, ImportExport.project_filename)
end end
...@@ -24,7 +25,29 @@ module Gitlab ...@@ -24,7 +25,29 @@ module Gitlab
private private
def project_json_tree def project_json_tree
@project.to_json(Gitlab::ImportExport::Reader.new(shared: @shared).project_tree) project_json['project_members'] += group_members_json
project_json.to_json
end
def project_json
@project_json ||= @project.as_json(reader.project_tree)
end
def reader
@reader ||= Gitlab::ImportExport::Reader.new(shared: @shared)
end
def group_members_json
group_members.as_json(reader.group_members_tree).each do |group_member|
group_member['source_type'] = 'Project' # Make group members project members of the future import
end
end
def group_members
return [] unless @current_user.can?(:admin_group, @project.group)
MembersFinder.new(@project.project_members, @project.group).execute(@current_user)
end end
end end
end end
......
...@@ -21,6 +21,10 @@ module Gitlab ...@@ -21,6 +21,10 @@ module Gitlab
false false
end end
def group_members_tree
@attributes_finder.find_included(:project_members).merge(include: @attributes_finder.find(:user))
end
private private
# Builds a hash in the format described here: http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html # Builds a hash in the format described here: http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
......
...@@ -89,7 +89,7 @@ module Gitlab ...@@ -89,7 +89,7 @@ module Gitlab
end end
def has_author?(old_author_id) def has_author?(old_author_id)
admin_user? && @members_mapper.map.keys.include?(old_author_id) admin_user? && @members_mapper.include?(old_author_id)
end end
def missing_author_note(updated_at, author_name) def missing_author_note(updated_at, author_name)
......
...@@ -116,5 +116,27 @@ describe Gitlab::ImportExport::MembersMapper, services: true do ...@@ -116,5 +116,27 @@ describe Gitlab::ImportExport::MembersMapper, services: true do
expect(members_mapper.map[exported_user_id]).to eq(user2.id) expect(members_mapper.map[exported_user_id]).to eq(user2.id)
end end
end end
context 'importing group members' do
let(:group) { create(:group) }
let(:project) { create(:empty_project, namespace: group) }
let(:members_mapper) do
described_class.new(
exported_members: exported_members, user: user, project: project)
end
before do
group.add_users([user, user2], GroupMember::DEVELOPER)
user.update(email: 'invite@test.com')
end
it 'maps the importer' do
expect(members_mapper.map[-1]).to eq(user.id)
end
it 'maps the group member' do
expect(members_mapper.map[exported_user_id]).to eq(user2.id)
end
end
end end
end end
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::ProjectTreeSaver, services: true do describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
describe 'saves the project tree into a json object' do describe 'saves the project tree into a json object' do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) }
let(:project_tree_saver) { described_class.new(project: project, shared: shared) } let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared) }
let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" } let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { setup_project } let(:project) { setup_project }
...@@ -92,7 +92,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -92,7 +92,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
end end
it 'has pipeline builds' do it 'has pipeline builds' do
expect(saved_project_json['pipelines'].first['statuses'].count { |hash| hash['type'] == 'Ci::Build'}).to eq(1) expect(saved_project_json['pipelines'].first['statuses'].count { |hash| hash['type'] == 'Ci::Build' }).to eq(1)
end end
it 'has pipeline commits' do it 'has pipeline commits' do
...@@ -112,13 +112,13 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -112,13 +112,13 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
end end
it 'has project and group labels' do it 'has project and group labels' do
label_types = saved_project_json['issues'].first['label_links'].map { |link| link['label']['type']} label_types = saved_project_json['issues'].first['label_links'].map { |link| link['label']['type'] }
expect(label_types).to match_array(['ProjectLabel', 'GroupLabel']) expect(label_types).to match_array(['ProjectLabel', 'GroupLabel'])
end end
it 'has priorities associated to labels' do it 'has priorities associated to labels' do
priorities = saved_project_json['issues'].first['label_links'].map { |link| link['label']['priorities']} priorities = saved_project_json['issues'].first['label_links'].map { |link| link['label']['priorities'] }
expect(priorities.flatten).not_to be_empty expect(priorities.flatten).not_to be_empty
end end
...@@ -140,6 +140,51 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -140,6 +140,51 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(project_tree_saver.save).to be true expect(project_tree_saver.save).to be true
end end
context 'group members' do
let(:user2) { create(:user, email: 'group@member.com') }
let(:member_emails) do
saved_project_json['project_members'].map do |pm|
pm['user']['email']
end
end
before do
Group.first.add_developer(user2)
end
it 'does not export group members if it has no permission' do
Group.first.add_developer(user)
expect(member_emails).not_to include('group@member.com')
end
it 'does not export group members as master' do
Group.first.add_master(user)
expect(member_emails).not_to include('group@member.com')
end
it 'exports group members as group owner' do
Group.first.add_owner(user)
expect(member_emails).to include('group@member.com')
end
context 'as admin' do
let(:user) { create(:admin) }
it 'exports group members as admin' do
expect(member_emails).to include('group@member.com')
end
it 'exports group members as project members' do
member_types = saved_project_json['project_members'].map { |pm| pm['source_type'] }
expect(member_types).to all(eq('Project'))
end
end
end
end end
end end
...@@ -170,10 +215,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -170,10 +215,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
commit_status = create(:commit_status, project: project) commit_status = create(:commit_status, project: project)
ci_pipeline = create(:ci_pipeline, ci_pipeline = create(:ci_pipeline,
project: project, project: project,
sha: merge_request.diff_head_sha, sha: merge_request.diff_head_sha,
ref: merge_request.source_branch, ref: merge_request.source_branch,
statuses: [commit_status]) statuses: [commit_status])
create(:ci_build, pipeline: ci_pipeline, project: project) create(:ci_build, pipeline: ci_pipeline, project: project)
create(:milestone, project: project) create(:milestone, project: project)
......
...@@ -86,6 +86,10 @@ describe Gitlab::ImportExport::Reader, lib: true do ...@@ -86,6 +86,10 @@ describe Gitlab::ImportExport::Reader, lib: true do
expect(described_class.new(shared: shared).project_tree).to match(include: [{ issues: { methods: [:name] } }]) expect(described_class.new(shared: shared).project_tree).to match(include: [{ issues: { methods: [:name] } }])
end end
it 'generates the correct hash for group members' do
expect(described_class.new(shared: shared).group_members_tree).to match({ include: { user: { only: [:email] } } })
end
def setup_yaml(hash) def setup_yaml(hash)
allow(YAML).to receive(:load_file).with(test_config).and_return(hash) allow(YAML).to receive(:load_file).with(test_config).and_return(hash)
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