Commit 3c69d253 authored by Stan Hu's avatar Stan Hu

Merge branch 'ce-to-ee-2018-07-02' into 'master'

CE upstream - 2018-07-02 09:22 UTC

Closes #5864

See merge request gitlab-org/gitlab-ee!6346
parents a11c8496 609a9558
...@@ -106,11 +106,11 @@ class Repository ...@@ -106,11 +106,11 @@ class Repository
"#<#{self.class.name}:#{@disk_path}>" "#<#{self.class.name}:#{@disk_path}>"
end end
def commit(ref = 'HEAD') def commit(ref = nil)
return nil unless exists? return nil unless exists?
return ref if ref.is_a?(::Commit) return ref if ref.is_a?(::Commit)
find_commit(ref) find_commit(ref || root_ref)
end end
# Finding a commit by the passed SHA # Finding a commit by the passed SHA
...@@ -290,6 +290,10 @@ class Repository ...@@ -290,6 +290,10 @@ class Repository
) )
end end
def cached_methods
CACHED_METHODS
end
def expire_tags_cache def expire_tags_cache
expire_method_caches(%i(tag_names tag_count)) expire_method_caches(%i(tag_names tag_count))
@tags = nil @tags = nil
...@@ -430,7 +434,7 @@ class Repository ...@@ -430,7 +434,7 @@ class Repository
# Runs code after the HEAD of a repository is changed. # Runs code after the HEAD of a repository is changed.
def after_change_head def after_change_head
expire_method_caches(METHOD_CACHES_FOR_FILE_TYPES.keys) expire_all_method_caches
end end
# Runs code after a repository has been forked/imported. # Runs code after a repository has been forked/imported.
......
---
title: Fix label and milestone duplicated records and IID errors
merge_request: 19961
author:
type: fixed
---
title: Expire correct method caches after HEAD changed
merge_request:
author:
type: fixed
...@@ -121,6 +121,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a ...@@ -121,6 +121,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
| `provider` | Always `AWS` for compatible hosts | AWS | | `provider` | Always `AWS` for compatible hosts | AWS |
| `aws_access_key_id` | AWS credentials, or compatible | | | `aws_access_key_id` | AWS credentials, or compatible | |
| `aws_secret_access_key` | AWS credentials, or compatible | | | `aws_secret_access_key` | AWS credentials, or compatible | |
| `aws_signature_version` | AWS signature version to use. 2 or 4 are valid options. Digital Ocean Spaces and other providers may need 2. | 4 |
| `region` | AWS region | us-east-1 | | `region` | AWS region | us-east-1 |
| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com | | `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
| `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) | | `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
......
...@@ -79,6 +79,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a ...@@ -79,6 +79,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
| `provider` | Always `AWS` for compatible hosts | AWS | | `provider` | Always `AWS` for compatible hosts | AWS |
| `aws_access_key_id` | AWS credentials, or compatible | | | `aws_access_key_id` | AWS credentials, or compatible | |
| `aws_secret_access_key` | AWS credentials, or compatible | | | `aws_secret_access_key` | AWS credentials, or compatible | |
| `aws_signature_version` | AWS signature version to use. 2 or 4 are valid options. Digital Ocean Spaces and other providers may need 2. | 4 |
| `region` | AWS region | us-east-1 | | `region` | AWS region | us-east-1 |
| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com | | `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
| `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) | | `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
......
...@@ -134,9 +134,20 @@ In order to do that, follow the steps: ...@@ -134,9 +134,20 @@ In order to do that, follow the steps:
```yaml ```yaml
image: docker:stable image: docker:stable
# When using dind, it's wise to use the overlayfs driver for
# improved performance.
variables: variables:
# When using dind service we need to instruct docker, to talk with the
# daemon started inside of the service. The daemon is available with
# a network connection instead of the default /var/run/docker.sock socket.
#
# The 'docker' hostname is the alias of the service container as described at
# https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services
#
# Note that if you're using Kubernetes executor, the variable should be set to
# tcp://localhost:2375 because of how Kubernetes executor connects services
# to the job container
DOCKER_HOST: tcp://docker:2375/
# When using dind, it's wise to use the overlayfs driver for
# improved performance.
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
services: services:
...@@ -293,6 +304,7 @@ services: ...@@ -293,6 +304,7 @@ services:
variables: variables:
CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
before_script: before_script:
...@@ -391,6 +403,9 @@ could look like: ...@@ -391,6 +403,9 @@ could look like:
image: docker:stable image: docker:stable
services: services:
- docker:dind - docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
stage: build stage: build
script: script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.example.com - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.example.com
...@@ -410,6 +425,8 @@ services: ...@@ -410,6 +425,8 @@ services:
- docker:dind - docker:dind
variables: variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
before_script: before_script:
...@@ -445,6 +462,8 @@ stages: ...@@ -445,6 +462,8 @@ stages:
- deploy - deploy
variables: variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
CONTAINER_TEST_IMAGE: registry.example.com/my-group/my-project/my-image:$CI_COMMIT_REF_NAME CONTAINER_TEST_IMAGE: registry.example.com/my-group/my-project/my-image:$CI_COMMIT_REF_NAME
CONTAINER_RELEASE_IMAGE: registry.example.com/my-group/my-project/my-image:latest CONTAINER_RELEASE_IMAGE: registry.example.com/my-group/my-project/my-image:latest
......
...@@ -326,6 +326,16 @@ For installations from source: ...@@ -326,6 +326,16 @@ For installations from source:
1. [Restart GitLab] for the changes to take effect 1. [Restart GitLab] for the changes to take effect
#### Specifying a custom directory for backups
Note: This option only works for remote storage. If you want to group your backups
you can pass a `DIRECTORY` environment variable:
```
sudo gitlab-rake gitlab:backup:create DIRECTORY=daily
sudo gitlab-rake gitlab:backup:create DIRECTORY=weekly
```
### Uploading to locally mounted shares ### Uploading to locally mounted shares
You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by
...@@ -369,15 +379,6 @@ For installations from source: ...@@ -369,15 +379,6 @@ For installations from source:
remote_directory: 'gitlab_backups' remote_directory: 'gitlab_backups'
``` ```
### Specifying a custom directory for backups
If you want to group your backups you can pass a `DIRECTORY` environment variable:
```
sudo gitlab-rake gitlab:backup:create DIRECTORY=daily
sudo gitlab-rake gitlab:backup:create DIRECTORY=weekly
```
### Backup archive permissions ### Backup archive permissions
The backup archives created by GitLab (`1393513186_2014_02_27_gitlab_backup.tar`) The backup archives created by GitLab (`1393513186_2014_02_27_gitlab_backup.tar`)
......
...@@ -90,6 +90,7 @@ Here is a configuration example with S3. ...@@ -90,6 +90,7 @@ Here is a configuration example with S3.
| `provider` | The provider name | AWS | | `provider` | The provider name | AWS |
| `aws_access_key_id` | AWS credentials, or compatible | `ABC123DEF456` | | `aws_access_key_id` | AWS credentials, or compatible | `ABC123DEF456` |
| `aws_secret_access_key` | AWS credentials, or compatible | `ABC123DEF456ABC123DEF456ABC123DEF456` | | `aws_secret_access_key` | AWS credentials, or compatible | `ABC123DEF456ABC123DEF456ABC123DEF456` |
| `aws_signature_version` | AWS signature version to use. 2 or 4 are valid options. Digital Ocean Spaces and other providers may need 2. | 4 |
| `region` | AWS region | us-east-1 | | `region` | AWS region | us-east-1 |
| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com | | `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
| `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) | | `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
......
module Gitlab
module ImportExport
# Given a class, it finds or creates a new object
# (initializes in the case of Label) at group or project level.
# If it does not exist in the group, it creates it at project level.
#
# Example:
# `GroupProjectObjectBuilder.build(Label, label_attributes)`
# finds or initializes a label with the given attributes.
#
# It also adds some logic around Group Labels/Milestones for edge cases.
class GroupProjectObjectBuilder
def self.build(*args)
Project.transaction do
new(*args).find
end
end
def initialize(klass, attributes)
@klass = klass < Label ? Label : klass
@attributes = attributes
@group = @attributes['group']
@project = @attributes['project']
end
def find
find_object || @klass.create(project_attributes)
end
private
def find_object
@klass.where(where_clause).first
end
def where_clause
@attributes.slice('title').map do |key, value|
scope_clause = table[:project_id].eq(@project.id)
scope_clause = scope_clause.or(table[:group_id].eq(@group.id)) if @group
table[key].eq(value).and(scope_clause)
end.reduce(:or)
end
def table
@table ||= @klass.arel_table
end
def project_attributes
@attributes.except('group').tap do |atts|
if label?
atts['type'] = 'ProjectLabel' # Always create project labels
elsif milestone?
if atts['group_id'] # Transform new group milestones into project ones
atts['iid'] = nil
atts.delete('group_id')
else
claim_iid
end
end
end
end
def label?
@klass == Label
end
def milestone?
@klass == Milestone
end
# If an existing group milestone used the IID
# claim the IID back and set the group milestone to use one available
# This is necessary to fix situations like the following:
# - Importing into a user namespace project with exported group milestones
# where the IID of the Group milestone could conflict with a project one.
def claim_iid
# The milestone has to be a group milestone, as it's the only case where
# we set the IID as the maximum. The rest of them are fixed.
milestone = @project.milestones.find_by(iid: @attributes['iid'])
return unless milestone
milestone.iid = nil
milestone.ensure_project_iid!
milestone.save!
end
end
end
end
module Gitlab module Gitlab
module ImportExport module ImportExport
class ProjectTreeRestorer class ProjectTreeRestorer
# Relations which cannot have both group_id and project_id at the same time # Relations which cannot be saved at project level (and have a group assigned)
RESTRICT_PROJECT_AND_GROUP = %i(milestone milestones).freeze GROUP_MODELS = [GroupLabel, Milestone].freeze
def initialize(user:, shared:, project:) def initialize(user:, shared:, project:)
@path = File.join(shared.export_path, 'project.json') @path = File.join(shared.export_path, 'project.json')
...@@ -70,12 +70,23 @@ module Gitlab ...@@ -70,12 +70,23 @@ module Gitlab
def save_relation_hash(relation_hash_batch, relation_key) def save_relation_hash(relation_hash_batch, relation_key)
relation_hash = create_relation(relation_key, relation_hash_batch) relation_hash = create_relation(relation_key, relation_hash_batch)
remove_group_models(relation_hash) if relation_hash.is_a?(Array)
@saved = false unless restored_project.append_or_update_attribute(relation_key, relation_hash) @saved = false unless restored_project.append_or_update_attribute(relation_key, relation_hash)
# Restore the project again, extra query that skips holding the AR objects in memory # Restore the project again, extra query that skips holding the AR objects in memory
@restored_project = Project.find(@project_id) @restored_project = Project.find(@project_id)
end end
# Remove project models that became group models as we found them at group level.
# This no longer required saving them at the root project level.
# For example, in the case of an existing group label that matched the title.
def remove_group_models(relation_hash)
relation_hash.reject! do |value|
GROUP_MODELS.include?(value.class) && value.group_id
end
end
def default_relation_list def default_relation_list
reader.tree.reject do |model| reader.tree.reject do |model|
model.is_a?(Hash) && model[:project_members] model.is_a?(Hash) && model[:project_members]
...@@ -170,7 +181,7 @@ module Gitlab ...@@ -170,7 +181,7 @@ module Gitlab
def create_relation(relation, relation_hash_list) def create_relation(relation, relation_hash_list)
relation_array = [relation_hash_list].flatten.map do |relation_hash| relation_array = [relation_hash_list].flatten.map do |relation_hash|
Gitlab::ImportExport::RelationFactory.create(relation_sym: relation.to_sym, Gitlab::ImportExport::RelationFactory.create(relation_sym: relation.to_sym,
relation_hash: parsed_relation_hash(relation_hash, relation.to_sym), relation_hash: relation_hash,
members_mapper: members_mapper, members_mapper: members_mapper,
user: @user, user: @user,
project: @restored_project, project: @restored_project,
...@@ -180,18 +191,6 @@ module Gitlab ...@@ -180,18 +191,6 @@ module Gitlab
relation_hash_list.is_a?(Array) ? relation_array : relation_array.first relation_hash_list.is_a?(Array) ? relation_array : relation_array.first
end end
def parsed_relation_hash(relation_hash, relation_type)
if RESTRICT_PROJECT_AND_GROUP.include?(relation_type)
params = {}
params['group_id'] = restored_project.group.try(:id) if relation_hash['group_id']
params['project_id'] = restored_project.id if relation_hash['project_id']
else
params = { 'group_id' => restored_project.group.try(:id), 'project_id' => restored_project.id }
end
relation_hash.merge(params)
end
def reader def reader
@reader ||= Gitlab::ImportExport::Reader.new(shared: @shared) @reader ||= Gitlab::ImportExport::Reader.new(shared: @shared)
end end
......
...@@ -55,6 +55,8 @@ module Gitlab ...@@ -55,6 +55,8 @@ module Gitlab
@project = project @project = project
@imported_object_retries = 0 @imported_object_retries = 0
@relation_hash['project_id'] = @project.id
# Remove excluded keys from relation_hash # Remove excluded keys from relation_hash
# We don't do this in the parsed_relation_hash because of the 'transformed attributes' # We don't do this in the parsed_relation_hash because of the 'transformed attributes'
# For example, MergeRequestDiffFiles exports its diff attribute as utf8_diff. Then, # For example, MergeRequestDiffFiles exports its diff attribute as utf8_diff. Then,
...@@ -81,15 +83,12 @@ module Gitlab ...@@ -81,15 +83,12 @@ module Gitlab
case @relation_name case @relation_name
when :merge_request_diff_files then setup_diff when :merge_request_diff_files then setup_diff
when :notes then setup_note when :notes then setup_note
when :project_label, :project_labels then setup_label
when :milestone, :milestones then setup_milestone
when 'Ci::Pipeline' then setup_pipeline when 'Ci::Pipeline' then setup_pipeline
else
@relation_hash['project_id'] = @project.id
end end
update_user_references update_user_references
update_project_references update_project_references
update_group_references
remove_duplicate_assignees remove_duplicate_assignees
reset_tokens! reset_tokens!
...@@ -152,39 +151,23 @@ module Gitlab ...@@ -152,39 +151,23 @@ module Gitlab
end end
def update_project_references def update_project_references
project_id = @relation_hash.delete('project_id')
# If source and target are the same, populate them with the new project ID. # If source and target are the same, populate them with the new project ID.
if @relation_hash['source_project_id'] if @relation_hash['source_project_id']
@relation_hash['source_project_id'] = same_source_and_target? ? project_id : MergeRequestParser::FORKED_PROJECT_ID @relation_hash['source_project_id'] = same_source_and_target? ? @relation_hash['project_id'] : MergeRequestParser::FORKED_PROJECT_ID
end end
# project_id may not be part of the export, but we always need to populate it if required. @relation_hash['target_project_id'] = @relation_hash['project_id'] if @relation_hash['target_project_id']
@relation_hash['project_id'] = project_id
@relation_hash['target_project_id'] = project_id if @relation_hash['target_project_id']
end end
def same_source_and_target? def same_source_and_target?
@relation_hash['target_project_id'] && @relation_hash['target_project_id'] == @relation_hash['source_project_id'] @relation_hash['target_project_id'] && @relation_hash['target_project_id'] == @relation_hash['source_project_id']
end end
def setup_label def update_group_references
# If there's no group, move the label to a project label return unless EXISTING_OBJECT_CHECK.include?(@relation_name)
if @relation_hash['type'] == 'GroupLabel' && @relation_hash['group_id'] return unless @relation_hash['group_id']
@relation_hash['project_id'] = nil
@relation_name = :group_label
else
@relation_hash['group_id'] = nil
@relation_hash['type'] = 'ProjectLabel'
end
end
def setup_milestone @relation_hash['group_id'] = @project.group&.id
if @relation_hash['group_id']
@relation_hash['group_id'] = @project.group.id
else
@relation_hash['project_id'] = @project.id
end
end end
def reset_tokens! def reset_tokens!
...@@ -272,15 +255,7 @@ module Gitlab ...@@ -272,15 +255,7 @@ module Gitlab
end end
def existing_object def existing_object
@existing_object ||= @existing_object ||= find_or_create_object!
begin
existing_object = find_or_create_object!
# 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
def unknown_service? def unknown_service?
...@@ -289,29 +264,16 @@ module Gitlab ...@@ -289,29 +264,16 @@ module Gitlab
end end
def find_or_create_object! def find_or_create_object!
finder_attributes = if @relation_name == :group_label return relation_class.find_or_create_by(project_id: @project.id) if @relation_name == :project_feature
%w[title group_id]
elsif parsed_relation_hash['project_id'] # Can't use IDs as validation exists calling `group` or `project` attributes
%w[title project_id] finder_hash = parsed_relation_hash.tap do |hash|
else hash['group'] = @project.group if relation_class.attribute_method?('group_id')
%w[title group_id] hash['project'] = @project
end hash.delete('project_id')
finder_hash = parsed_relation_hash.slice(*finder_attributes)
if label?
label = relation_class.find_or_initialize_by(finder_hash)
parsed_relation_hash.delete('priorities') if label.persisted?
label.save!
label
else
relation_class.find_or_create_by(finder_hash)
end end
end
def label? GroupProjectObjectBuilder.build(relation_class, finder_hash)
@relation_name.to_s.include?('label')
end end
end end
end end
......
...@@ -25,6 +25,11 @@ module Gitlab ...@@ -25,6 +25,11 @@ module Gitlab
raise NotImplementedError raise NotImplementedError
end end
# List of cached methods. Should be overridden by the including class
def cached_methods
raise NotImplementedError
end
# Caches the supplied block both in a cache and in an instance variable. # Caches the supplied block both in a cache and in an instance variable.
# #
# The cache key and instance variable are named the same way as the value of # The cache key and instance variable are named the same way as the value of
...@@ -67,6 +72,11 @@ module Gitlab ...@@ -67,6 +72,11 @@ module Gitlab
# Expires the caches of a specific set of methods # Expires the caches of a specific set of methods
def expire_method_caches(methods) def expire_method_caches(methods)
methods.each do |key| methods.each do |key|
unless cached_methods.include?(key.to_sym)
Rails.logger.error "Requested to expire non-existent method '#{key}' for Repository"
next
end
cache.expire(key) cache.expire(key)
ivar = cache_instance_variable_name(key) ivar = cache_instance_variable_name(key)
......
require 'spec_helper'
describe Gitlab::ImportExport::GroupProjectObjectBuilder do
let(:project) do
create(:project,
:builds_disabled,
:issues_disabled,
name: 'project',
path: 'project',
group: create(:group))
end
context 'labels' do
it 'finds the right group label' do
group_label = create(:group_label, 'name': 'group label', 'group': project.group)
expect(described_class.build(Label,
'title' => 'group label',
'project' => project,
'group' => project.group)).to eq(group_label)
end
it 'creates a new label' do
label = described_class.build(Label,
'title' => 'group label',
'project' => project,
'group' => project.group)
expect(label.persisted?).to be true
end
end
context 'milestones' do
it 'finds the right group milestone' do
milestone = create(:milestone, 'name' => 'group milestone', 'group' => project.group)
expect(described_class.build(Milestone,
'title' => 'group milestone',
'project' => project,
'group' => project.group)).to eq(milestone)
end
it 'creates a new milestone' do
milestone = described_class.build(Milestone,
'title' => 'group milestone',
'project' => project,
'group' => project.group)
expect(milestone.persisted?).to be true
end
end
end
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
"milestones": [ "milestones": [
{ {
"id": 1, "id": 1,
"title": "Project milestone", "title": "A milestone",
"project_id": 8, "project_id": 8,
"description": "Project-level milestone", "description": "Project-level milestone",
"due_date": null, "due_date": null,
...@@ -66,8 +66,8 @@ ...@@ -66,8 +66,8 @@
"group_milestone_id": null, "group_milestone_id": null,
"milestone": { "milestone": {
"id": 1, "id": 1,
"title": "Project milestone", "title": "A milestone",
"project_id": 8, "group_id": 8,
"description": "Project-level milestone", "description": "Project-level milestone",
"due_date": null, "due_date": null,
"created_at": "2016-06-14T15:02:04.415Z", "created_at": "2016-06-14T15:02:04.415Z",
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
"updated_at": "2017-08-15T18:37:40.795Z", "updated_at": "2017-08-15T18:37:40.795Z",
"label": { "label": {
"id": 6, "id": 6,
"title": "Another project label", "title": "Another label",
"color": "#A8D695", "color": "#A8D695",
"project_id": null, "project_id": null,
"created_at": "2017-08-15T18:37:19.698Z", "created_at": "2017-08-15T18:37:19.698Z",
......
{
"description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
"import_type": "gitlab_project",
"creator_id": 123,
"visibility_level": 10,
"archived": false,
"issues": [
{
"id": 1,
"title": "Fugiat est minima quae maxime non similique.",
"assignee_id": null,
"project_id": 8,
"author_id": 1,
"created_at": "2017-07-07T18:13:01.138Z",
"updated_at": "2017-08-15T18:37:40.807Z",
"branch_name": null,
"description": "Quam totam fuga numquam in eveniet.",
"state": "opened",
"iid": 20,
"updated_by_id": 1,
"confidential": false,
"due_date": null,
"moved_to_id": null,
"lock_version": null,
"time_estimate": 0,
"closed_at": null,
"last_edited_at": null,
"last_edited_by_id": null,
"group_milestone_id": null,
"milestone": {
"id": 1,
"title": "Group-level milestone",
"description": "Group-level milestone",
"due_date": null,
"created_at": "2016-06-14T15:02:04.415Z",
"updated_at": "2016-06-14T15:02:04.415Z",
"state": "active",
"iid": 1,
"group_id": 8
}
},
{
"id": 2,
"title": "est minima quae maxime non similique.",
"assignee_id": null,
"project_id": 8,
"author_id": 1,
"created_at": "2017-07-07T18:13:01.138Z",
"updated_at": "2017-08-15T18:37:40.807Z",
"branch_name": null,
"description": "Quam totam fuga numquam in eveniet.",
"state": "opened",
"iid": 21,
"updated_by_id": 1,
"confidential": false,
"due_date": null,
"moved_to_id": null,
"lock_version": null,
"time_estimate": 0,
"closed_at": null,
"last_edited_at": null,
"last_edited_by_id": null,
"group_milestone_id": null,
"milestone": {
"id": 2,
"title": "Another milestone",
"project_id": 8,
"description": "milestone",
"due_date": null,
"created_at": "2016-06-14T15:02:04.415Z",
"updated_at": "2016-06-14T15:02:04.415Z",
"state": "active",
"iid": 1,
"group_id": null
}
}
],
"snippets": [],
"hooks": []
}
...@@ -189,8 +189,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -189,8 +189,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
@project.pipelines.zip([2, 2, 2, 2, 2]) @project.pipelines.zip([2, 2, 2, 2, 2])
.each do |(pipeline, expected_status_size)| .each do |(pipeline, expected_status_size)|
expect(pipeline.statuses.size).to eq(expected_status_size) expect(pipeline.statuses.size).to eq(expected_status_size)
end end
end end
end end
...@@ -246,13 +246,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -246,13 +246,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(project.issues.size).to eq(results.fetch(:issues, 0)) expect(project.issues.size).to eq(results.fetch(:issues, 0))
end end
it 'has issue with group label and project label' do
labels = project.issues.first.labels
expect(labels.where(type: "ProjectLabel").count).to eq(results.fetch(:first_issue_labels, 0))
expect(labels.where(type: "ProjectLabel").where.not(group_id: nil).count).to eq(0)
end
it 'does not set params that are excluded from import_export settings' do it 'does not set params that are excluded from import_export settings' do
expect(project.import_type).to be_nil expect(project.import_type).to be_nil
expect(project.creator_id).not_to eq 123 expect(project.creator_id).not_to eq 123
...@@ -268,12 +261,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -268,12 +261,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it 'has group milestone' do it 'has group milestone' do
expect(project.group.milestones.size).to eq(results.fetch(:milestones, 0)) expect(project.group.milestones.size).to eq(results.fetch(:milestones, 0))
end end
it 'has issue with group label' do
labels = project.issues.first.labels
expect(labels.where(type: "GroupLabel").count).to eq(results.fetch(:first_issue_labels, 0))
end
end end
context 'Light JSON' do context 'Light JSON' do
...@@ -360,13 +347,72 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -360,13 +347,72 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it_behaves_like 'restores project correctly', it_behaves_like 'restores project correctly',
issues: 2, issues: 2,
labels: 1, labels: 1,
milestones: 1, milestones: 2,
first_issue_labels: 1 first_issue_labels: 1
it_behaves_like 'restores group correctly', it_behaves_like 'restores group correctly',
labels: 1, labels: 0,
milestones: 1, milestones: 0,
first_issue_labels: 1 first_issue_labels: 1
end end
context 'with existing group models' do
let!(:project) do
create(:project,
:builds_disabled,
:issues_disabled,
name: 'project',
path: 'project',
group: create(:group))
end
before do
project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json")
end
it 'imports labels' do
create(:group_label, name: 'Another label', group: project.group)
expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error)
restored_project_json
expect(project.labels.count).to eq(1)
end
it 'imports milestones' do
create(:milestone, name: 'A milestone', group: project.group)
expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error)
restored_project_json
expect(project.group.milestones.count).to eq(1)
expect(project.milestones.count).to eq(0)
end
end
context 'with clashing milestones on IID' do
let!(:project) do
create(:project,
:builds_disabled,
:issues_disabled,
name: 'project',
path: 'project',
group: create(:group))
end
it 'preserves the project milestone IID' do
project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.milestone-iid.json")
expect_any_instance_of(Gitlab::ImportExport::Shared).not_to receive(:error)
restored_project_json
expect(project.milestones.count).to eq(2)
expect(Milestone.find_by_title('Another milestone').iid).to eq(1)
expect(Milestone.find_by_title('Group-level milestone').iid).to eq(2)
end
end
end end
end end
...@@ -67,10 +67,18 @@ describe Gitlab::RepositoryCacheAdapter do ...@@ -67,10 +67,18 @@ describe Gitlab::RepositoryCacheAdapter do
describe '#expire_method_caches' do describe '#expire_method_caches' do
it 'expires the caches of the given methods' do it 'expires the caches of the given methods' do
expect(cache).to receive(:expire).with(:readme) expect(cache).to receive(:expire).with(:rendered_readme)
expect(cache).to receive(:expire).with(:gitignore) expect(cache).to receive(:expire).with(:gitignore)
repository.expire_method_caches(%i(readme gitignore)) repository.expire_method_caches(%i(rendered_readme gitignore))
end
it 'does not expire caches for non-existent methods' do
expect(cache).not_to receive(:expire).with(:nonexistent)
expect(Rails.logger).to(
receive(:error).with("Requested to expire non-existent method 'nonexistent' for Repository"))
repository.expire_method_caches(%i(nonexistent))
end end
end end
end end
...@@ -479,6 +479,14 @@ describe Repository do ...@@ -479,6 +479,14 @@ describe Repository do
end end
end end
context 'when ref is not specified' do
it 'is using a root ref' do
expect(repository).to receive(:find_commit).with('master')
repository.commit
end
end
context 'when ref is not valid' do context 'when ref is not valid' do
context 'when preceding tree element exists' do context 'when preceding tree element exists' do
it 'returns nil' do it 'returns nil' do
...@@ -1689,19 +1697,29 @@ describe Repository do ...@@ -1689,19 +1697,29 @@ describe Repository do
end end
describe '#after_change_head' do describe '#after_change_head' do
it 'flushes the readme cache' do it 'flushes the method caches' do
expect(repository).to receive(:expire_method_caches).with([ expect(repository).to receive(:expire_method_caches).with([
:readme, :size,
:commit_count,
:rendered_readme,
:contribution_guide,
:changelog, :changelog,
:license, :license_blob,
:contributing, :license_key,
:gitignore, :gitignore,
:koding, :koding_yml,
:gitlab_ci, :gitlab_ci_yml,
:branch_names,
:tag_names,
:branch_count,
:tag_count,
:avatar, :avatar,
:issue_template, :exists?,
:merge_request_template, :root_ref,
:xcode_config :has_visible_content?,
:issue_template_names,
:merge_request_template_names,
:xcode_project?
]) ])
repository.after_change_head repository.after_change_head
......
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