Merge branch 'fix/import-export-db-errors' into 'master'

Fix import/export database errors

Fixes protected branches errors when importing a project including them

Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/21295

Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/21799

See merge request !6099
This commit is contained in:
Rémy Coutable 2016-09-19 13:02:49 +00:00
commit 1e72de6690
11 changed files with 160 additions and 27 deletions

View File

@ -150,6 +150,7 @@ v 8.12.0 (unreleased)
- Fix Gitlab::Popen.popen thread-safety issue - Fix Gitlab::Popen.popen thread-safety issue
- Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska)
- Clean environment variables when running git hooks - Clean environment variables when running git hooks
- Fix Import/Export issues importing protected branches and some specific models
- Fix non-master branch readme display in tree view - Fix non-master branch readme display in tree view
v 8.11.6 v 8.11.6

View File

@ -2,6 +2,7 @@ module Ci
class Pipeline < ActiveRecord::Base class Pipeline < ActiveRecord::Base
extend Ci::Model extend Ci::Model
include HasStatus include HasStatus
include Importable
self.table_name = 'ci_commits' self.table_name = 'ci_commits'
@ -12,12 +13,12 @@ module Ci
has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
validates_presence_of :sha validates_presence_of :sha, unless: :importing?
validates_presence_of :ref validates_presence_of :ref, unless: :importing?
validates_presence_of :status validates_presence_of :status, unless: :importing?
validate :valid_commit_sha validate :valid_commit_sha, unless: :importing?
after_save :keep_around_commits after_save :keep_around_commits, unless: :importing?
delegate :stages, to: :statuses delegate :stages, to: :statuses

View File

@ -3,8 +3,8 @@
>**Notes:** >**Notes:**
> >
> - [Introduced][ce-3050] in GitLab 8.9. > - [Introduced][ce-3050] in GitLab 8.9.
> - Importing will not be possible if the import instance version is lower > - Importing will not be possible if the import instance version differs from
> than that of the exporter. > that of the exporter.
> - For existing installations, the project import option has to be enabled in > - For existing installations, the project import option has to be enabled in
> application settings (`/admin/application_settings`) under 'Import sources'. > application settings (`/admin/application_settings`) under 'Import sources'.
> You will have to be an administrator to enable and use the import functionality. > You will have to be an administrator to enable and use the import functionality.
@ -17,6 +17,20 @@
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.
## Version history
| GitLab version | Import/Export version |
| -------- | -------- |
| 8.12.0 to current | 0.1.4 |
| 8.10.3 | 0.1.3 |
| 8.10.0 | 0.1.2 |
| 8.9.5 | 0.1.1 |
| 8.9.0 | 0.1.0 |
> The table reflects what GitLab version we updated the Import/Export version at.
> For instance, 8.10.3 and 8.11 will have the same Import/Export version (0.1.3)
> and the exports between them will be compatible.
## Exported contents ## Exported contents
The following items will be exported: The following items will be exported:

View File

@ -2,7 +2,8 @@ module Gitlab
module ImportExport module ImportExport
extend self extend self
VERSION = '0.1.3' # For every version update, the version history in import_export.md has to be kept up to date.
VERSION = '0.1.4'
FILENAME_LIMIT = 50 FILENAME_LIMIT = 50
def export_path(relative_path:) def export_path(relative_path:)

View File

@ -35,7 +35,9 @@ project_tree:
- :deploy_keys - :deploy_keys
- :services - :services
- :hooks - :hooks
- :protected_branches - protected_branches:
- :merge_access_levels
- :push_access_levels
- :labels - :labels
- milestones: - milestones:
- :events - :events

View File

@ -7,7 +7,9 @@ module Gitlab
variables: 'Ci::Variable', variables: 'Ci::Variable',
triggers: 'Ci::Trigger', triggers: 'Ci::Trigger',
builds: 'Ci::Build', builds: 'Ci::Build',
hooks: 'ProjectHook' }.freeze hooks: 'ProjectHook',
merge_access_levels: 'ProtectedBranch::MergeAccessLevel',
push_access_levels: 'ProtectedBranch::PushAccessLevel' }.freeze
USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze
@ -17,6 +19,8 @@ module Gitlab
EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze
FINDER_ATTRIBUTES = %w[title project_id].freeze
def self.create(*args) def self.create(*args)
new(*args).create new(*args).create
end end
@ -149,7 +153,7 @@ module Gitlab
end end
def parsed_relation_hash def parsed_relation_hash
@relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) } @parsed_relation_hash ||= @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
end end
def set_st_diffs def set_st_diffs
@ -161,14 +165,30 @@ module Gitlab
# Otherwise always create the record, skipping the extra SELECT clause. # Otherwise always create the record, skipping the extra SELECT clause.
@existing_or_new_object ||= begin @existing_or_new_object ||= begin
if EXISTING_OBJECT_CHECK.include?(@relation_name) if EXISTING_OBJECT_CHECK.include?(@relation_name)
existing_object = relation_class.find_or_initialize_by(parsed_relation_hash.slice('title', 'project_id')) events = parsed_relation_hash.delete('events')
existing_object.assign_attributes(parsed_relation_hash)
unless events.blank?
existing_object.assign_attributes(events: events)
end
existing_object existing_object
else else
relation_class.new(parsed_relation_hash) relation_class.new(parsed_relation_hash)
end end
end end
end end
def existing_object
@existing_object ||=
begin
finder_hash = parsed_relation_hash.slice(*FINDER_ATTRIBUTES)
existing_object = relation_class.find_or_create_by(finder_hash)
# Done in two steps, as MySQL behaves differently than PostgreSQL using
# the +find_or_create_by+ method and does not return the ID the second time.
existing_object.update(parsed_relation_hash)
existing_object
end
end
end end
end end
end end

View File

@ -24,8 +24,8 @@ module Gitlab
end end
def verify_version!(version) def verify_version!(version)
if Gem::Version.new(version) > Gem::Version.new(Gitlab::ImportExport.version) if Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version)
raise Gitlab::ImportExport::Error.new("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}") raise Gitlab::ImportExport::Error.new("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}")
else else
true true
end end

View File

@ -24,7 +24,7 @@
"test_ee_field": "test", "test_ee_field": "test",
"milestone": { "milestone": {
"id": 1, "id": 1,
"title": "v0.0", "title": "test milestone",
"project_id": 8, "project_id": 8,
"description": "test milestone", "description": "test milestone",
"due_date": null, "due_date": null,
@ -51,7 +51,7 @@
{ {
"id": 2, "id": 2,
"label_id": 2, "label_id": 2,
"target_id": 3, "target_id": 40,
"target_type": "Issue", "target_type": "Issue",
"created_at": "2016-07-22T08:57:02.840Z", "created_at": "2016-07-22T08:57:02.840Z",
"updated_at": "2016-07-22T08:57:02.840Z", "updated_at": "2016-07-22T08:57:02.840Z",
@ -281,6 +281,31 @@
"deleted_at": null, "deleted_at": null,
"due_date": null, "due_date": null,
"moved_to_id": null, "moved_to_id": null,
"milestone": {
"id": 1,
"title": "test milestone",
"project_id": 8,
"description": "test milestone",
"due_date": null,
"created_at": "2016-06-14T15:02:04.415Z",
"updated_at": "2016-06-14T15:02:04.415Z",
"state": "active",
"iid": 1,
"events": [
{
"id": 487,
"target_type": "Milestone",
"target_id": 1,
"title": null,
"data": null,
"project_id": 46,
"created_at": "2016-06-14T15:02:04.418Z",
"updated_at": "2016-06-14T15:02:04.418Z",
"action": 1,
"author_id": 18
}
]
},
"notes": [ "notes": [
{ {
"id": 359, "id": 359,
@ -494,6 +519,27 @@
"deleted_at": null, "deleted_at": null,
"due_date": null, "due_date": null,
"moved_to_id": null, "moved_to_id": null,
"label_links": [
{
"id": 99,
"label_id": 2,
"target_id": 38,
"target_type": "Issue",
"created_at": "2016-07-22T08:57:02.840Z",
"updated_at": "2016-07-22T08:57:02.840Z",
"label": {
"id": 2,
"title": "test2",
"color": "#428bca",
"project_id": 8,
"created_at": "2016-07-22T08:55:44.161Z",
"updated_at": "2016-07-22T08:55:44.161Z",
"template": false,
"description": "",
"priority": null
}
}
],
"notes": [ "notes": [
{ {
"id": 367, "id": 367,
@ -6478,7 +6524,7 @@
{ {
"id": 37, "id": 37,
"project_id": 5, "project_id": 5,
"ref": "master", "ref": null,
"sha": "048721d90c449b244b7b4c53a9186b04330174ec", "sha": "048721d90c449b244b7b4c53a9186b04330174ec",
"before_sha": null, "before_sha": null,
"push_data": null, "push_data": null,
@ -7301,6 +7347,30 @@
], ],
"protected_branches": [ "protected_branches": [
{
"id": 1,
"project_id": 9,
"name": "master",
"created_at": "2016-08-30T07:32:52.426Z",
"updated_at": "2016-08-30T07:32:52.426Z",
"merge_access_levels": [
{
"id": 1,
"protected_branch_id": 1,
"access_level": 40,
"created_at": "2016-08-30T07:32:52.458Z",
"updated_at": "2016-08-30T07:32:52.458Z"
}
],
"push_access_levels": [
{
"id": 1,
"protected_branch_id": 1,
"access_level": 40,
"created_at": "2016-08-30T07:32:52.490Z",
"updated_at": "2016-08-30T07:32:52.490Z"
}
]
}
] ]
} }

View File

@ -29,12 +29,30 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::ENABLED) expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::ENABLED)
end end
it 'has the same label associated to two issues' do
restored_project_json
expect(Label.first.issues.count).to eq(2)
end
it 'has milestones associated to two separate issues' do
restored_project_json
expect(Milestone.find_by_description('test milestone').issues.count).to eq(2)
end
it 'creates a valid pipeline note' do it 'creates a valid pipeline note' do
restored_project_json restored_project_json
expect(Ci::Pipeline.first.notes).not_to be_empty expect(Ci::Pipeline.first.notes).not_to be_empty
end end
it 'restores pipelines with missing ref' do
restored_project_json
expect(Ci::Pipeline.where(ref: nil)).not_to be_empty
end
it 'restores the correct event with symbolised data' do it 'restores the correct event with symbolised data' do
restored_project_json restored_project_json
@ -49,6 +67,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC') expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC')
end end
it 'contains the merge access levels on a protected branch' do
restored_project_json
expect(ProtectedBranch.first.merge_access_levels).not_to be_empty
end
it 'contains the push access levels on a protected branch' do
restored_project_json
expect(ProtectedBranch.first.push_access_levels).not_to be_empty
end
context 'event at forth level of the tree' do context 'event at forth level of the tree' do
let(:event) { Event.where(title: 'test levels').first } let(:event) { Event.where(title: 'test levels').first }
@ -77,12 +107,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(Label.first.label_links.first.target).not_to be_nil expect(Label.first.label_links.first.target).not_to be_nil
end end
it 'has milestones associated to issues' do
restored_project_json
expect(Milestone.find_by_description('test milestone').issues).not_to be_empty
end
context 'Merge requests' do context 'Merge requests' do
before do before do
restored_project_json restored_project_json

View File

@ -23,7 +23,7 @@ describe Gitlab::ImportExport::VersionChecker, services: true do
it 'shows the correct error message' do it 'shows the correct error message' do
described_class.check!(shared: shared) described_class.check!(shared: shared)
expect(shared.errors.first).to eq("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}") expect(shared.errors.first).to eq("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}")
end end
end end
end end