summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/pages.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml2
-rw-r--r--CHANGELOG-EE.md4
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/models/snippet_repository.rb6
-rw-r--r--app/services/projects/import_export/export_service.rb6
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml4
-rw-r--r--changelogs/unreleased/198323-migrate-snippet-mentions-to-db-table.yml6
-rw-r--r--changelogs/unreleased/208827-replace-issue-external-icon-with-external-link.yml5
-rw-r--r--changelogs/unreleased/fj-39201-import-export-project-snippets.yml5
-rw-r--r--db/post_migrate/20200127111953_cleanup_empty_snippet_user_mentions.rb28
-rw-r--r--db/post_migrate/20200127131953_migrate_snippet_mentions_to_db.rb35
-rw-r--r--db/post_migrate/20200127141953_add_temporary_snippet_notes_with_mentions_index.rb20
-rw-r--r--db/post_migrate/20200127151953_migrate_snippet_notes_mentions_to_db.rb38
-rw-r--r--db/schema.rb1
-rw-r--r--doc/user/group/saml_sso/index.md40
-rw-r--r--doc/user/profile/account/two_factor_authentication.md1
-rw-r--r--lib/api/api.rb2
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/snippet.rb43
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/snippet_user_mention.rb18
-rw-r--r--lib/gitlab/import_export.rb12
-rw-r--r--lib/gitlab/import_export/importer.rb8
-rw-r--r--lib/gitlab/import_export/snippet_repo_restorer.rb48
-rw-r--r--lib/gitlab/import_export/snippet_repo_saver.rb21
-rw-r--r--lib/gitlab/import_export/snippets_repo_restorer.rb36
-rw-r--r--lib/gitlab/import_export/snippets_repo_saver.rb34
-rwxr-xr-xscripts/review_apps/automated_cleanup.rb2
-rw-r--r--spec/factories/snippets.rb2
-rw-r--r--spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb77
-rw-r--r--spec/lib/gitlab/import_export/import_export_spec.rb8
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb3
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb70
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb48
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb55
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb56
-rw-r--r--spec/migrations/cleanup_empty_snippet_user_mentions_spec.rb42
-rw-r--r--spec/migrations/migrate_snippet_mentions_to_db_spec.rb28
-rw-r--r--spec/migrations/migrate_snippet_notes_mentions_to_db_spec.rb31
-rw-r--r--spec/models/snippet_repository_spec.rb38
-rw-r--r--spec/services/projects/import_export/export_service_spec.rb8
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb2
41 files changed, 521 insertions, 378 deletions
diff --git a/.gitlab/ci/pages.gitlab-ci.yml b/.gitlab/ci/pages.gitlab-ci.yml
index 993ed21e39d..7c6f25cfe6b 100644
--- a/.gitlab/ci/pages.gitlab-ci.yml
+++ b/.gitlab/ci/pages.gitlab-ci.yml
@@ -5,7 +5,7 @@ pages:
- .default-cache
- .pages:rules
stage: pages
- dependencies: ["coverage", "karma", "gitlab:assets:compile pull-cache"]
+ dependencies: ["rspec:coverage", "karma", "gitlab:assets:compile pull-cache"]
script:
- mv public/ .public/
- mkdir public/
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 26f7febb59b..2f014824c91 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -202,7 +202,7 @@ gitlab:setup:
paths:
- log/development.log
-coverage:
+rspec:coverage:
extends:
- .rails-job-base
- .rails:rules:ee-and-foss
diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md
index 2f6792c6bea..911e071caee 100644
--- a/CHANGELOG-EE.md
+++ b/CHANGELOG-EE.md
@@ -1,5 +1,9 @@
Please view this file on the master branch, on stable branches it's out of date.
+## 12.8.5
+
+- No changes.
+
## 12.8.4
- No changes.
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 27e1778e9b6..5c2e03e4b9c 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -302,6 +302,10 @@ class Snippet < ApplicationRecord
field != :content || MarkupHelper.gitlab_markdown?(file_name)
end
+ def hexdigest
+ Digest::SHA256.hexdigest("#{title}#{description}#{created_at}#{updated_at}")
+ end
+
class << self
# Searches for snippets with a matching title or file name.
#
diff --git a/app/models/snippet_repository.rb b/app/models/snippet_repository.rb
index 10580c51098..89098971a7d 100644
--- a/app/models/snippet_repository.rb
+++ b/app/models/snippet_repository.rb
@@ -18,6 +18,12 @@ class SnippetRepository < ApplicationRecord
end
end
+ def create_file(user, path, content, **options)
+ options[:actions] = transform_file_entries([{ file_path: path, content: content }])
+
+ capture_git_error { repository.multi_action(user, **options) }
+ end
+
def multi_files_action(user, files = [], **options)
return if files.nil? || files.empty?
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index 5a3eb4c2156..74d5af41a04 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -42,7 +42,7 @@ module Projects
end
def exporters
- [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver, lfs_saver]
+ [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver, lfs_saver, snippets_repo_saver]
end
def version_saver
@@ -73,6 +73,10 @@ module Projects
Gitlab::ImportExport::LfsSaver.new(project: project, shared: shared)
end
+ def snippets_repo_saver
+ Gitlab::ImportExport::SnippetsRepoSaver.new(current_user: current_user, project: project, shared: shared)
+ end
+
def cleanup
FileUtils.rm_rf(shared.archive_path) if shared&.archive_path
end
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index b8b5eacfab1..5afe43d6636 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -143,7 +143,7 @@
- issue_tracker = @project.external_issue_tracker
= link_to issue_tracker.issue_tracker_path, class: 'shortcuts-external_tracker' do
.nav-icon-container
- = sprite_icon('issue-external')
+ = sprite_icon('external-link')
%span.nav-item-name
= issue_tracker.title
%ul.sidebar-sub-level-items.is-fly-out-only
@@ -319,7 +319,7 @@
= nav_link do
= link_to external_wiki_url, class: 'shortcuts-external_wiki' do
.nav-icon-container
- = sprite_icon('issue-external')
+ = sprite_icon('external-link')
%span.nav-item-name
= _('External Wiki')
%ul.sidebar-sub-level-items.is-fly-out-only
diff --git a/changelogs/unreleased/198323-migrate-snippet-mentions-to-db-table.yml b/changelogs/unreleased/198323-migrate-snippet-mentions-to-db-table.yml
deleted file mode 100644
index 7501e713c6c..00000000000
--- a/changelogs/unreleased/198323-migrate-snippet-mentions-to-db-table.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Migrate mentions for snippet and snippet notes to snippet_user_mentions DB
- table
-merge_request: 23783
-author:
-type: changed
diff --git a/changelogs/unreleased/208827-replace-issue-external-icon-with-external-link.yml b/changelogs/unreleased/208827-replace-issue-external-icon-with-external-link.yml
new file mode 100644
index 00000000000..a929facdbbc
--- /dev/null
+++ b/changelogs/unreleased/208827-replace-issue-external-icon-with-external-link.yml
@@ -0,0 +1,5 @@
+---
+title: Replace issue-external icon with external-link
+merge_request: 208827
+author:
+type: other
diff --git a/changelogs/unreleased/fj-39201-import-export-project-snippets.yml b/changelogs/unreleased/fj-39201-import-export-project-snippets.yml
new file mode 100644
index 00000000000..790052f3f20
--- /dev/null
+++ b/changelogs/unreleased/fj-39201-import-export-project-snippets.yml
@@ -0,0 +1,5 @@
+---
+title: Import/Export snippet repositories
+merge_request: 24150
+author:
+type: added
diff --git a/db/post_migrate/20200127111953_cleanup_empty_snippet_user_mentions.rb b/db/post_migrate/20200127111953_cleanup_empty_snippet_user_mentions.rb
deleted file mode 100644
index aad688fef3f..00000000000
--- a/db/post_migrate/20200127111953_cleanup_empty_snippet_user_mentions.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-class CleanupEmptySnippetUserMentions < ActiveRecord::Migration[5.2]
- DOWNTIME = false
- BATCH_SIZE = 10_000
-
- class SnippetUserMention < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'snippet_user_mentions'
- end
-
- def up
- # cleanup snippet user mentions with no actual mentions,
- # re https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24586#note_285982468
- SnippetUserMention
- .where(mentioned_users_ids: nil)
- .where(mentioned_groups_ids: nil)
- .where(mentioned_projects_ids: nil)
- .each_batch(of: BATCH_SIZE) do |batch|
- batch.delete_all
- end
- end
-
- def down
- # no-op
- end
-end
diff --git a/db/post_migrate/20200127131953_migrate_snippet_mentions_to_db.rb b/db/post_migrate/20200127131953_migrate_snippet_mentions_to_db.rb
deleted file mode 100644
index e25c2c2982a..00000000000
--- a/db/post_migrate/20200127131953_migrate_snippet_mentions_to_db.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-class MigrateSnippetMentionsToDb < ActiveRecord::Migration[5.2]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
- DELAY = 2.minutes.to_i
- BATCH_SIZE = 10_000
- MIGRATION = 'UserMentions::CreateResourceUserMention'
-
- JOIN = "LEFT JOIN snippet_user_mentions on snippets.id = snippet_user_mentions.snippet_id"
- QUERY_CONDITIONS = "(description like '%@%' OR title like '%@%') AND snippet_user_mentions.snippet_id IS NULL"
-
- disable_ddl_transaction!
-
- class Snippet < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'snippets'
- end
-
- def up
- Snippet
- .joins(JOIN)
- .where(QUERY_CONDITIONS)
- .each_batch(of: BATCH_SIZE) do |batch, index|
- range = batch.pluck(Arel.sql('MIN(snippets.id)'), Arel.sql('MAX(snippets.id)')).first
- migrate_in(index * DELAY, MIGRATION, ['Snippet', JOIN, QUERY_CONDITIONS, false, *range])
- end
- end
-
- def down
- # no-op
- end
-end
diff --git a/db/post_migrate/20200127141953_add_temporary_snippet_notes_with_mentions_index.rb b/db/post_migrate/20200127141953_add_temporary_snippet_notes_with_mentions_index.rb
deleted file mode 100644
index ec9b8b76f6f..00000000000
--- a/db/post_migrate/20200127141953_add_temporary_snippet_notes_with_mentions_index.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-class AddTemporarySnippetNotesWithMentionsIndex < ActiveRecord::Migration[5.2]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
- INDEX_NAME = 'snippet_mentions_temp_index'
- INDEX_CONDITION = "note LIKE '%@%'::text AND notes.noteable_type = 'Snippet'"
-
- disable_ddl_transaction!
-
- def up
- # create temporary index for notes with mentions, may take well over 1h
- add_concurrent_index(:notes, :id, where: INDEX_CONDITION, name: INDEX_NAME)
- end
-
- def down
- remove_concurrent_index(:notes, :id, where: INDEX_CONDITION, name: INDEX_NAME)
- end
-end
diff --git a/db/post_migrate/20200127151953_migrate_snippet_notes_mentions_to_db.rb b/db/post_migrate/20200127151953_migrate_snippet_notes_mentions_to_db.rb
deleted file mode 100644
index 3795a96b426..00000000000
--- a/db/post_migrate/20200127151953_migrate_snippet_notes_mentions_to_db.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-class MigrateSnippetNotesMentionsToDb < ActiveRecord::Migration[5.2]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
- DELAY = 2.minutes.to_i
- BATCH_SIZE = 10_000
- MIGRATION = 'UserMentions::CreateResourceUserMention'
-
- INDEX_CONDITION = "note LIKE '%@%'::text AND notes.noteable_type = 'Snippet'"
- QUERY_CONDITIONS = "#{INDEX_CONDITION} AND snippet_user_mentions.snippet_id IS NULL"
- JOIN = 'INNER JOIN snippets ON snippets.id = notes.noteable_id LEFT JOIN snippet_user_mentions ON notes.id = snippet_user_mentions.note_id'
-
- disable_ddl_transaction!
-
- class Note < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'notes'
- end
-
- def up
- Note
- .joins(JOIN)
- .where(QUERY_CONDITIONS)
- .each_batch(of: BATCH_SIZE) do |batch, index|
- range = batch.pluck(Arel.sql('MIN(notes.id)'), Arel.sql('MAX(notes.id)')).first
- migrate_in(index * DELAY, MIGRATION, ['Snippet', JOIN, QUERY_CONDITIONS, true, *range])
- end
- end
-
- def down
- # no-op
- # temporary index is to be dropped in a different migration in an upcoming release:
- # https://gitlab.com/gitlab-org/gitlab/issues/196842
- end
-end
diff --git a/db/schema.rb b/db/schema.rb
index a9cc6d89c6d..8cfa949548f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -2838,7 +2838,6 @@ ActiveRecord::Schema.define(version: 2020_03_04_160823) do
t.index ["discussion_id"], name: "index_notes_on_discussion_id"
t.index ["id"], name: "design_mentions_temp_index", where: "((note ~~ '%@%'::text) AND ((noteable_type)::text = 'DesignManagement::Design'::text))"
t.index ["id"], name: "epic_mentions_temp_index", where: "((note ~~ '%@%'::text) AND ((noteable_type)::text = 'Epic'::text))"
- t.index ["id"], name: "snippet_mentions_temp_index", where: "((note ~~ '%@%'::text) AND ((noteable_type)::text = 'Snippet'::text))"
t.index ["line_code"], name: "index_notes_on_line_code"
t.index ["note"], name: "index_notes_on_note_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["note"], name: "tmp_idx_on_promoted_notes", where: "(((noteable_type)::text = 'Issue'::text) AND (system IS TRUE) AND (note ~~ 'promoted to epic%'::text))"
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index e81ce4c15cb..0d9c6024601 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -234,6 +234,29 @@ Recommended `NameID` value: `OneLogin ID`.
Set parameters according to the [assertions table](#assertions).
+### Additional setup options
+
+GitLab [isn't limited to the SAML providers listed above](#my-identity-provider-isnt-listed) but your Identity Provider may require additional configuration, such as the following:
+
+| Field | Value | Notes |
+|-------|-------|-------|
+| SAML Profile | Web browser SSO profile | GitLab uses SAML to sign users in via their browser. We don't make requests direct to the Identity Provider. |
+| SAML Request Binding | HTTP Redirect | GitLab (the service provider) redirects users to your Identity Provider with a base64 encoded `SAMLRequest` HTTP parameter. |
+| SAML Response Binding | HTTP POST | Your Identity Provider responds to users with an HTTP form including the `SAMLResponse`, which a user's browser submits back to GitLab. |
+| Sign SAML Response | Yes | We require this to prevent tampering. |
+| X509 Certificate in response | Yes | This is used to sign the response and checked against the provided fingerprint. |
+| Fingerprint Algorithm | SHA-1 | We need a SHA-1 hash of the certificate used to sign the SAML Response. |
+| Signature Algorithm | SHA-1/SHA-256/SHA-384/SHA-512 | Also known as the Digest Method, this can be specified in the SAML response. It determines how a response is signed. |
+| Encrypt SAML Assertion | No | TLS is used between your Identity Provider, the user's browser, and GitLab. |
+| Sign SAML Assertion | Optional | We don't require Assertions to be signed. We validate their integrity by requiring the whole response to be signed. |
+| Check SAML Request Signature | No | GitLab does not sign SAML requests, but does check the signature on the SAML response. |
+| Default RelayState | Optional | The URL users should end up on after signing in via a button on your Identity Provider. |
+| NameID Format | `Persistent` | See [details above](#nameid-format). |
+| Additional URLs | | You may need to use the `Identifier` or `Assertion consumer service URL` in other fields on some providers. |
+| Single Sign Out URL | | Not supported |
+
+If the information information you need isn't listed above you may wish to check our [troubleshooting docs below](#i-need-additional-information-to-configure-my-identity-provider).
+
## Linking SAML to your existing GitLab.com account
To link SAML to your existing GitLab.com account:
@@ -320,3 +343,20 @@ To change which identity you sign in with, you can [unlink the previous SAML ide
Getting both of these errors at the same time suggests the NameID capitalization provided by the Identity Provider didn't exactly match the previous value for that user.
This can be prevented by configuring the [NameID](#nameid) to return a consistent value. Fixing this for an individual user involves [unlinking SAML in the GitLab account](#unlinking-accounts), although this will cause group membership and Todos to be lost.
+
+### My identity provider isn't listed
+
+Not a problem, the SAML standard means that a wide range of identity providers will work with GitLab. Unfortunately we aren't familiar with all of them so can only offer support configuring the [listed providers](#providers).
+
+### I need additional information to configure my identity provider
+
+Many SAML terms can vary between providers. It is possible that the information you are looking for is listed under another name.
+
+For more information, start with your Identity Provider's documentation. Look for their options and examples to see how they configure SAML. This can provide hints on what you'll need to configure GitLab to work with these providers.
+
+It can also help to look at our [more detailed docs for self-managed GitLab](../../../integration/saml.md).
+SAML configuration for GitLab.com is mostly the same as for self-managed instances.
+However, self-managed GitLab instances use a configuration file that supports more options as described in the external [OmniAuth SAML documentation](https://github.com/omniauth/omniauth-saml/).
+Internally that uses the [`ruby-saml` library](https://github.com/onelogin/ruby-saml), so we sometimes check there to verify low level details of less commonly used options.
+
+It can also help to compare the XML response from your provider with our [example XML used for internal testing](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/spec/fixtures/saml/response.xml).
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index 56261d13395..2949a2d4fc8 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -110,6 +110,7 @@ the **Download codes** button for storage in a safe place. If you choose to
download them, the file will be called `gitlab-recovery-codes.txt`.
If you lose the recovery codes or just want to generate new ones, you can do so
+from the [two-factor authentication account settings page](#regenerate-2fa-recovery-codes) or
[using SSH](#generate-new-recovery-codes-using-ssh).
## Logging in with 2FA Enabled
diff --git a/lib/api/api.rb b/lib/api/api.rb
index ca6e6de3342..02b3fe7e03e 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -45,7 +45,7 @@ module API
before do
Gitlab::ApplicationContext.push(
- user: -> { current_user },
+ user: -> { @current_user },
project: -> { @project },
namespace: -> { @group },
caller_id: route.origin
diff --git a/lib/gitlab/background_migration/user_mentions/models/snippet.rb b/lib/gitlab/background_migration/user_mentions/models/snippet.rb
deleted file mode 100644
index cdbada76429..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/snippet.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- class Snippet < ActiveRecord::Base
- include Concerns::IsolatedMentionable
- include Concerns::MentionableMigrationMethods
- include CacheMarkdownField
-
- attr_mentionable :title, pipeline: :single_line
- attr_mentionable :description
- cache_markdown_field :title, pipeline: :single_line
- cache_markdown_field :description
-
- self.table_name = 'snippets'
- self.inheritance_column = :_type_disabled
-
- belongs_to :author, class_name: "User"
- belongs_to :project
-
- def self.user_mention_model
- Gitlab::BackgroundMigration::UserMentions::Models::SnippetUserMention
- end
-
- def user_mention_model
- self.class.user_mention_model
- end
-
- def user_mention_resource_id
- id
- end
-
- def user_mention_note_id
- 'NULL'
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/snippet_user_mention.rb b/lib/gitlab/background_migration/user_mentions/models/snippet_user_mention.rb
deleted file mode 100644
index a856a53626e..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/snippet_user_mention.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- class SnippetUserMention < ActiveRecord::Base
- self.table_name = 'snippet_user_mentions'
-
- def self.resource_foreign_key
- :snippet_id
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index 1033e6c4e05..52102b6f508 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -42,6 +42,18 @@ module Gitlab
"project.wiki.bundle"
end
+ def snippet_repo_bundle_dir
+ 'snippets'
+ end
+
+ def snippets_repo_bundle_path(absolute_path)
+ File.join(absolute_path, ::Gitlab::ImportExport.snippet_repo_bundle_dir)
+ end
+
+ def snippet_repo_bundle_filename_for(snippet)
+ "#{snippet.hexdigest}.bundle"
+ end
+
def config_file
Rails.root.join('lib/gitlab/import_export/project/import_export.yml')
end
diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb
index 4eeecc14067..4b761eb86ae 100644
--- a/lib/gitlab/import_export/importer.rb
+++ b/lib/gitlab/import_export/importer.rb
@@ -35,7 +35,7 @@ module Gitlab
def restorers
[repo_restorer, wiki_restorer, project_tree, avatar_restorer,
- uploads_restorer, lfs_restorer, statistics_restorer]
+ uploads_restorer, lfs_restorer, statistics_restorer, snippets_repo_restorer]
end
def import_file
@@ -79,6 +79,12 @@ module Gitlab
Gitlab::ImportExport::LfsRestorer.new(project: project, shared: shared)
end
+ def snippets_repo_restorer
+ Gitlab::ImportExport::SnippetsRepoRestorer.new(project: project,
+ shared: shared,
+ user: current_user)
+ end
+
def statistics_restorer
Gitlab::ImportExport::StatisticsRestorer.new(project: project, shared: shared)
end
diff --git a/lib/gitlab/import_export/snippet_repo_restorer.rb b/lib/gitlab/import_export/snippet_repo_restorer.rb
new file mode 100644
index 00000000000..079681dfac5
--- /dev/null
+++ b/lib/gitlab/import_export/snippet_repo_restorer.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ImportExport
+ class SnippetRepoRestorer < RepoRestorer
+ attr_reader :snippet
+
+ def initialize(snippet:, user:, shared:, path_to_bundle:)
+ @snippet = snippet
+ @user = user
+ @repository = snippet.repository
+ @path_to_bundle = path_to_bundle.to_s
+ @shared = shared
+ end
+
+ def restore
+ if File.exist?(path_to_bundle)
+ create_repository_from_bundle
+ else
+ create_repository_from_db
+ end
+
+ true
+ rescue => e
+ shared.error(e)
+ false
+ end
+
+ private
+
+ def create_repository_from_bundle
+ repository.create_from_bundle(path_to_bundle)
+ snippet.track_snippet_repository
+ end
+
+ def create_repository_from_db
+ snippet.create_repository
+
+ commit_attrs = {
+ branch_name: 'master',
+ message: 'Initial commit'
+ }
+
+ repository.create_file(@user, snippet.file_name, snippet.content, commit_attrs)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/snippet_repo_saver.rb b/lib/gitlab/import_export/snippet_repo_saver.rb
new file mode 100644
index 00000000000..cab96c78232
--- /dev/null
+++ b/lib/gitlab/import_export/snippet_repo_saver.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ImportExport
+ class SnippetRepoSaver < RepoSaver
+ def initialize(project:, shared:, repository:)
+ @project = project
+ @shared = shared
+ @repository = repository
+ end
+
+ private
+
+ def bundle_full_path
+ File.join(shared.export_path,
+ ::Gitlab::ImportExport.snippet_repo_bundle_dir,
+ ::Gitlab::ImportExport.snippet_repo_bundle_filename_for(repository.container))
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/snippets_repo_restorer.rb b/lib/gitlab/import_export/snippets_repo_restorer.rb
new file mode 100644
index 00000000000..8fe83225812
--- /dev/null
+++ b/lib/gitlab/import_export/snippets_repo_restorer.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ImportExport
+ class SnippetsRepoRestorer
+ def initialize(project:, shared:, user:)
+ @project = project
+ @shared = shared
+ @user = user
+ end
+
+ def restore
+ return true unless Feature.enabled?(:version_snippets, @user)
+ return true unless Dir.exist?(snippets_repo_bundle_path)
+
+ @project.snippets.find_each.all? do |snippet|
+ Gitlab::ImportExport::SnippetRepoRestorer.new(snippet: snippet,
+ user: @user,
+ shared: @shared,
+ path_to_bundle: snippet_repo_bundle_path(snippet))
+ .restore
+ end
+ end
+
+ private
+
+ def snippet_repo_bundle_path(snippet)
+ File.join(snippets_repo_bundle_path, ::Gitlab::ImportExport.snippet_repo_bundle_filename_for(snippet))
+ end
+
+ def snippets_repo_bundle_path
+ @snippets_repo_bundle_path ||= ::Gitlab::ImportExport.snippets_repo_bundle_path(@shared.export_path)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/snippets_repo_saver.rb b/lib/gitlab/import_export/snippets_repo_saver.rb
new file mode 100644
index 00000000000..85e094c0d15
--- /dev/null
+++ b/lib/gitlab/import_export/snippets_repo_saver.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ImportExport
+ class SnippetsRepoSaver
+ include Gitlab::ImportExport::CommandLineUtil
+
+ def initialize(current_user:, project:, shared:)
+ @project = project
+ @shared = shared
+ @current_user = current_user
+ end
+
+ def save
+ return true unless Feature.enabled?(:version_snippets, @current_user)
+
+ create_snippets_repo_directory
+
+ @project.snippets.find_each.all? do |snippet|
+ Gitlab::ImportExport::SnippetRepoSaver.new(project: @project,
+ shared: @shared,
+ repository: snippet.repository)
+ .save
+ end
+ end
+
+ private
+
+ def create_snippets_repo_directory
+ mkdir_p(::Gitlab::ImportExport.snippets_repo_bundle_path(@shared.export_path))
+ end
+ end
+ end
+end
diff --git a/scripts/review_apps/automated_cleanup.rb b/scripts/review_apps/automated_cleanup.rb
index 323a1deedfc..93412b658f6 100755
--- a/scripts/review_apps/automated_cleanup.rb
+++ b/scripts/review_apps/automated_cleanup.rb
@@ -81,7 +81,7 @@ class AutomatedCleanup
release = Quality::HelmClient::Release.new(environment.slug, 1, deployed_at.to_s, nil, nil, review_apps_namespace)
releases_to_delete << release
end
- elsif deployed_at < stop_threshold
+ elsif environment.state != 'stopped' && deployed_at < stop_threshold
stop_environment(environment, deployment)
else
print_release_state(subject: 'Review App', release_name: environment.slug, release_date: last_deploy, action: 'leaving')
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index 2cdb8019696..8ab5c7f1fa5 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -34,7 +34,7 @@ FactoryBot.define do
trait :empty_repo do
after(:create) do |snippet|
- raise "Failed to create repository!" unless snippet.repository.create_if_not_exists
+ raise "Failed to create repository!" unless snippet.create_repository
end
end
end
diff --git a/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb b/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
deleted file mode 100644
index 9c085b3cef8..00000000000
--- a/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require './db/post_migrate/20200127131953_migrate_snippet_mentions_to_db'
-require './db/post_migrate/20200127151953_migrate_snippet_notes_mentions_to_db'
-
-describe Gitlab::BackgroundMigration::UserMentions::CreateResourceUserMention, schema: 20200127151953 do
- include MigrationsHelpers
-
- context 'when migrating data' do
- let(:users) { table(:users) }
- let(:namespaces) { table(:namespaces) }
- let(:projects) { table(:projects) }
- let(:notes) { table(:notes) }
-
- let(:author) { users.create!(email: 'author@example.com', notification_email: 'author@example.com', name: 'author', username: 'author', projects_limit: 10, state: 'active') }
- let(:member) { users.create!(email: 'member@example.com', notification_email: 'member@example.com', name: 'member', username: 'member', projects_limit: 10, state: 'active') }
- let(:admin) { users.create!(email: 'administrator@example.com', notification_email: 'administrator@example.com', name: 'administrator', username: 'administrator', admin: 1, projects_limit: 10, state: 'active') }
- let(:john_doe) { users.create!(email: 'john_doe@example.com', notification_email: 'john_doe@example.com', name: 'john_doe', username: 'john_doe', projects_limit: 10, state: 'active') }
- let(:skipped) { users.create!(email: 'skipped@example.com', notification_email: 'skipped@example.com', name: 'skipped', username: 'skipped', projects_limit: 10, state: 'active') }
-
- let(:mentioned_users) { [author, member, admin, john_doe, skipped] }
- let(:mentioned_users_refs) { mentioned_users.map { |u| "@#{u.username}" }.join(' ') }
-
- let(:group) { namespaces.create!(name: 'test1', path: 'test1', runners_token: 'my-token1', project_creation_level: 1, visibility_level: 20, type: 'Group') }
- let(:inaccessible_group) { namespaces.create!(name: 'test2', path: 'test2', runners_token: 'my-token2', project_creation_level: 1, visibility_level: 0, type: 'Group') }
- let(:project) { projects.create!(name: 'gitlab1', path: 'gitlab1', namespace_id: group.id, visibility_level: 0) }
-
- let(:mentioned_groups) { [group, inaccessible_group] }
- let(:group_mentions) { [group, inaccessible_group].map { |gr| "@#{gr.path}" }.join(' ') }
- let(:description_mentions) { "description with mentions #{mentioned_users_refs} and #{group_mentions}" }
-
- before do
- # build personal namespaces and routes for users
- mentioned_users.each { |u| u.becomes(User).save! }
-
- # build namespaces and routes for groups
- mentioned_groups.each do |gr|
- gr.name += '-org'
- gr.path += '-org'
- gr.becomes(Namespace).save!
- end
- end
-
- context 'migrate snippet mentions' do
- let(:snippets) { table(:snippets) }
- let(:snippet_user_mentions) { table(:snippet_user_mentions) }
-
- let!(:snippet1) { snippets.create!(project_id: project.id, author_id: author.id, title: 'title1', description: description_mentions) }
- let!(:snippet2) { snippets.create!(project_id: project.id, author_id: author.id, title: 'title2', description: 'some description') }
- let!(:snippet3) { snippets.create!(project_id: project.id, author_id: author.id, title: 'title3', description: 'description with an email@example.com and some other @ char here.') }
-
- let(:user_mentions) { snippet_user_mentions }
- let(:resource) { snippet1 }
-
- it_behaves_like 'resource mentions migration', MigrateSnippetMentionsToDb, Snippet
-
- context 'mentions in note' do
- let!(:note1) { notes.create!(noteable_id: snippet1.id, noteable_type: 'Snippet', project_id: project.id, author_id: author.id, note: description_mentions) }
- let!(:note2) { notes.create!(noteable_id: snippet1.id, noteable_type: 'Snippet', project_id: project.id, author_id: author.id, note: 'sample note') }
- let!(:note3) { notes.create!(noteable_id: snippet1.id, noteable_type: 'Snippet', project_id: project.id, author_id: author.id, note: description_mentions, system: true) }
- # this not does not have actual mentions
- let!(:note4) { notes.create!(noteable_id: snippet1.id, noteable_type: 'Snippet', project_id: project.id, author_id: author.id, note: 'note3 for an email@somesite.com and some other rando @ ref' ) }
- # this note points to an innexistent noteable record in snippets table
- let!(:note5) { notes.create!(noteable_id: snippets.maximum(:id) + 10, noteable_type: 'Snippet', project_id: project.id, author_id: author.id, note: description_mentions) }
-
- it_behaves_like 'resource notes mentions migration', MigrateSnippetNotesMentionsToDb, Snippet
- end
- end
- end
-
- context 'checks no_quote_columns' do
- it 'has correct no_quote_columns' do
- expect(Gitlab::BackgroundMigration::UserMentions::Models::Snippet.no_quote_columns).to match([:note_id, :snippet_id])
- end
- end
-end
diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb
index 2ece0dd4b56..300ba66ee5b 100644
--- a/spec/lib/gitlab/import_export/import_export_spec.rb
+++ b/spec/lib/gitlab/import_export/import_export_spec.rb
@@ -21,4 +21,12 @@ describe Gitlab::ImportExport do
expect(described_class.export_filename(exportable: project).length).to be < 70
end
end
+
+ describe '#snippet_repo_bundle_filename_for' do
+ let(:snippet) { build(:snippet, id: 1) }
+
+ it 'generates the snippet bundle name' do
+ expect(described_class.snippet_repo_bundle_filename_for(snippet)).to eq "#{snippet.hexdigest}.bundle"
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index 07857269004..e03c95525df 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -50,7 +50,8 @@ describe Gitlab::ImportExport::Importer do
Gitlab::ImportExport::WikiRestorer,
Gitlab::ImportExport::UploadsRestorer,
Gitlab::ImportExport::LfsRestorer,
- Gitlab::ImportExport::StatisticsRestorer
+ Gitlab::ImportExport::StatisticsRestorer,
+ Gitlab::ImportExport::SnippetsRepoRestorer
].each do |restorer|
it "calls the #{restorer}" do
fake_restorer = double(restorer.to_s)
diff --git a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
new file mode 100644
index 00000000000..d72d41ddf38
--- /dev/null
+++ b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::SnippetRepoRestorer do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, namespace: user.namespace) }
+ let(:snippet) { create(:project_snippet, project: project, author: user) }
+
+ let(:shared) { project.import_export_shared }
+ let(:exporter) { Gitlab::ImportExport::SnippetsRepoSaver.new(project: project, shared: shared, current_user: user) }
+ let(:restorer) do
+ described_class.new(user: user,
+ shared: shared,
+ snippet: snippet,
+ path_to_bundle: snippet_bundle_path)
+ end
+
+ after do
+ FileUtils.rm_rf(shared.export_path)
+ end
+
+ shared_examples 'no bundle file present' do
+ it 'creates the repository from the database content' do
+ expect(snippet.repository_exists?).to be_falsey
+
+ aggregate_failures do
+ expect(restorer.restore).to be_truthy
+
+ expect(snippet.repository_exists?).to be_truthy
+ expect(snippet.snippet_repository).not_to be_nil
+
+ blob = snippet.repository.blob_at('HEAD', snippet.file_name)
+ expect(blob).not_to be_nil
+ expect(blob.data).to eq(snippet.content)
+ end
+ end
+ end
+
+ context 'when the snippet does not have a bundle file path' do
+ let(:snippet_bundle_path) { nil }
+
+ it_behaves_like 'no bundle file present'
+ end
+
+ context 'when the snippet bundle path is not present' do
+ let(:snippet_bundle_path) { 'foo' }
+
+ it_behaves_like 'no bundle file present'
+ end
+
+ context 'when the snippet bundle exists' do
+ let!(:snippet_with_repo) { create(:project_snippet, :repository, project: project) }
+ let(:bundle_path) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) }
+ let(:snippet_bundle_path) { File.join(bundle_path, "#{snippet_with_repo.hexdigest}.bundle") }
+ let(:result) { exporter.save }
+
+ it 'creates the repository from the bundle' do
+ expect(exporter.save).to be_truthy
+
+ expect(snippet.repository_exists?).to be_falsey
+ expect(snippet.snippet_repository).to be_nil
+ expect(snippet.repository).to receive(:create_from_bundle).and_call_original
+
+ expect(restorer.restore).to be_truthy
+ expect(snippet.repository_exists?).to be_truthy
+ expect(snippet.snippet_repository).not_to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
new file mode 100644
index 00000000000..7ad1ff213a1
--- /dev/null
+++ b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::SnippetRepoSaver do
+ describe 'bundle a project Git repo' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, namespace: user.namespace) }
+ let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) }
+ let(:shared) { project.import_export_shared }
+ let(:bundler) { described_class.new(project: project, shared: shared, repository: snippet.repository) }
+ let(:bundle_path) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) }
+
+ around do |example|
+ FileUtils.mkdir_p(bundle_path)
+ example.run
+ ensure
+ FileUtils.rm_rf(bundle_path)
+ end
+
+ context 'with project snippet' do
+ it 'bundles the repo successfully' do
+ aggregate_failures do
+ expect(bundler.save).to be_truthy
+ expect(Dir.empty?(bundle_path)).to be_falsey
+ end
+ end
+
+ context 'when snippet does not have a repository' do
+ let(:snippet) { build(:personal_snippet) }
+
+ it 'returns true' do
+ expect(bundler.save).to be_truthy
+ end
+
+ it 'does not create any file' do
+ aggregate_failures do
+ expect(snippet.repository).not_to receive(:bundle_to_disk)
+
+ bundler.save
+
+ expect(Dir.empty?(bundle_path)).to be_truthy
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
new file mode 100644
index 00000000000..242f6f6b58c
--- /dev/null
+++ b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::SnippetsRepoRestorer do
+ include GitHelpers
+
+ describe 'bundle a snippet Git repo' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, namespace: user.namespace) }
+ let_it_be(:snippet_with_repo) { create(:project_snippet, :repository, project: project, author: user) }
+ let_it_be(:snippet_without_repo) { create(:project_snippet, project: project, author: user) }
+
+ let(:shared) { project.import_export_shared }
+ let(:exporter) { Gitlab::ImportExport::SnippetsRepoSaver.new(current_user: user, project: project, shared: shared) }
+ let(:bundle_dir) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) }
+ let(:restorer) do
+ described_class.new(user: user,
+ shared: shared,
+ project: project)
+ end
+ let(:service) { instance_double(Gitlab::ImportExport::SnippetRepoRestorer) }
+
+ before do
+ exporter.save
+ end
+
+ after do
+ FileUtils.rm_rf(shared.export_path)
+ end
+
+ it 'calls SnippetRepoRestorer per each snippet with the bundle path' do
+ allow(service).to receive(:restore).and_return(true)
+
+ expect(Gitlab::ImportExport::SnippetRepoRestorer).to receive(:new).with(hash_including(snippet: snippet_with_repo, path_to_bundle: bundle_path(snippet_with_repo))).and_return(service)
+ expect(Gitlab::ImportExport::SnippetRepoRestorer).to receive(:new).with(hash_including(snippet: snippet_without_repo, path_to_bundle: bundle_path(snippet_without_repo))).and_return(service)
+
+ expect(restorer.restore).to be_truthy
+ end
+
+ context 'when one snippet cannot be saved' do
+ it 'returns false and do not process other snippets' do
+ allow(Gitlab::ImportExport::SnippetRepoRestorer).to receive(:new).with(hash_including(snippet: snippet_with_repo)).and_return(service)
+ allow(service).to receive(:restore).and_return(false)
+
+ expect(Gitlab::ImportExport::SnippetRepoRestorer).not_to receive(:new).with(hash_including(snippet: snippet_without_repo))
+ expect(restorer.restore).to be_falsey
+ end
+ end
+
+ def bundle_path(snippet)
+ File.join(bundle_dir, ::Gitlab::ImportExport.snippet_repo_bundle_filename_for(snippet))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
new file mode 100644
index 00000000000..5332990a975
--- /dev/null
+++ b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::SnippetsRepoSaver do
+ describe 'bundle a project Git repo' do
+ let_it_be(:user) { create(:user) }
+ let!(:project) { create(:project) }
+ let(:shared) { project.import_export_shared }
+ let(:bundler) { described_class.new(current_user: user, project: project, shared: shared) }
+
+ after do
+ FileUtils.rm_rf(shared.export_path)
+ end
+
+ it 'creates the snippet bundles dir if not exists' do
+ snippets_dir = ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path)
+ expect(Dir.exist?(snippets_dir)).to be_falsey
+
+ bundler.save
+
+ expect(Dir.exist?(snippets_dir)).to be_truthy
+ end
+
+ context 'when project does not have any snippet' do
+ it 'does not perform any action' do
+ expect(Gitlab::ImportExport::SnippetRepoSaver).not_to receive(:new)
+
+ bundler.save
+ end
+ end
+
+ context 'when project has snippets' do
+ let!(:snippet1) { create(:project_snippet, :repository, project: project, author: user) }
+ let!(:snippet2) { create(:project_snippet, project: project, author: user) }
+ let(:service) { instance_double(Gitlab::ImportExport::SnippetRepoSaver) }
+
+ it 'calls the SnippetRepoSaver for each snippet' do
+ allow(Gitlab::ImportExport::SnippetRepoSaver).to receive(:new).and_return(service)
+ expect(service).to receive(:save).and_return(true).twice
+
+ bundler.save
+ end
+
+ context 'when one snippet cannot be saved' do
+ it 'returns false and do not process other snippets' do
+ allow(Gitlab::ImportExport::SnippetRepoSaver).to receive(:new).with(hash_including(repository: snippet1.repository)).and_return(service)
+ allow(service).to receive(:save).and_return(false)
+
+ expect(Gitlab::ImportExport::SnippetRepoSaver).not_to receive(:new).with(hash_including(repository: snippet2.repository))
+ expect(bundler.save).to be_falsey
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/cleanup_empty_snippet_user_mentions_spec.rb b/spec/migrations/cleanup_empty_snippet_user_mentions_spec.rb
deleted file mode 100644
index d229f0b2b59..00000000000
--- a/spec/migrations/cleanup_empty_snippet_user_mentions_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20200127111953_cleanup_empty_snippet_user_mentions')
-
-describe CleanupEmptySnippetUserMentions, :migration, :sidekiq do
- let(:users) { table(:users) }
- let(:projects) { table(:projects) }
- let(:namespaces) { table(:namespaces) }
- let(:snippets) { table(:snippets) }
- let(:snippet_user_mentions) { table(:snippet_user_mentions) }
- let(:notes) { table(:notes) }
-
- let(:user) { users.create!(name: 'root', email: 'root@example.com', username: 'root', projects_limit: 0) }
- let(:group) { namespaces.create!(name: 'group1', path: 'group1', owner_id: user.id) }
- let(:project) { projects.create!(name: 'gitlab1', path: 'gitlab1', namespace_id: group.id, visibility_level: 0) }
- let(:snippet) { snippets.create!(title: "title1", title_html: 'title1', description: 'snippet description with @root mention', project_id: project.id, author_id: user.id) }
-
- let!(:resource1) { notes.create!(note: 'note for @root to check', noteable_id: snippet.id, noteable_type: 'Snippet') }
- let!(:resource2) { notes.create!(note: 'note for @root to check', noteable_id: snippet.id, noteable_type: 'Snippet', system: true) }
- let!(:resource3) { notes.create!(note: 'note for @root to check', noteable_id: snippet.id, noteable_type: 'Snippet') }
-
- # non-migrateable resources
- # this note is already migrated, as it has a record in the snippet_user_mentions table
- let!(:resource4) { notes.create!(note: 'note for @root to check', noteable_id: snippet.id, noteable_type: 'Snippet') }
- let!(:user_mention) { snippet_user_mentions.create!(snippet_id: snippet.id, note_id: resource4.id, mentioned_users_ids: [1]) }
- # this note points to an innexistent noteable record
- let!(:resource5) { notes.create!(note: 'note for @root to check', noteable_id: snippets.maximum(:id) + 10, noteable_type: 'Snippet') }
-
- # these should get cleanup, by the migration
- let!(:blank_snippet_user_mention1) { snippet_user_mentions.create!(snippet_id: snippet.id, note_id: resource1.id)}
- let!(:blank_snippet_user_mention2) { snippet_user_mentions.create!(snippet_id: snippet.id, note_id: resource2.id)}
- let!(:blank_snippet_user_mention3) { snippet_user_mentions.create!(snippet_id: snippet.id, note_id: resource3.id)}
-
- it 'cleanups blank user mentions' do
- expect(snippet_user_mentions.count).to eq 4
-
- migrate!
-
- expect(snippet_user_mentions.count).to eq 1
- end
-end
diff --git a/spec/migrations/migrate_snippet_mentions_to_db_spec.rb b/spec/migrations/migrate_snippet_mentions_to_db_spec.rb
deleted file mode 100644
index 6644329fc11..00000000000
--- a/spec/migrations/migrate_snippet_mentions_to_db_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20200127131953_migrate_snippet_mentions_to_db')
-
-describe MigrateSnippetMentionsToDb, :migration, :sidekiq do
- let(:users) { table(:users) }
- let(:namespaces) { table(:namespaces) }
- let(:projects) { table(:projects) }
- let(:snippets) { table(:snippets) }
- let(:snippet_user_mentions) { table(:snippet_user_mentions) }
-
- let(:user) { users.create!(name: 'root', email: 'root@example.com', username: 'root', projects_limit: 0) }
- let(:group) { namespaces.create!(name: 'group1', path: 'group1', owner_id: user.id) }
- let(:project) { projects.create!(name: 'gitlab1', path: 'gitlab1', namespace_id: group.id, visibility_level: 0) }
- let!(:resource1) { snippets.create!(title: "title1", title_html: 'title1', description: 'snippet description with @root mention', project_id: project.id, author_id: user.id) }
- let!(:resource2) { snippets.create!(title: "title2", title_html: "title2", description: 'snippet description with @group mention', project_id: project.id, author_id: user.id) }
- let!(:resource3) { snippets.create!(title: "title3", title_html: "title3", description: 'snippet description with @project mention', project_id: project.id, author_id: user.id) }
-
- # non-migrateable resources
- # this snippet is already migrated, as it has a record in the snippet_user_mentions table
- let!(:resource4) { snippets.create!(title: "title4", title_html: "title4", description: 'snippet description with @project mention', project_id: project.id, author_id: user.id) }
- let!(:user_mention) { snippet_user_mentions.create!(snippet_id: resource4.id, mentioned_users_ids: [1]) }
- # this snippet has no mentions so should be filtered out
- let!(:resource5) { snippets.create!(title: "title5", title_html: "title5", description: 'snippet description with no mention', project_id: project.id, author_id: user.id) }
-
- it_behaves_like 'schedules resource mentions migration', Snippet, false
-end
diff --git a/spec/migrations/migrate_snippet_notes_mentions_to_db_spec.rb b/spec/migrations/migrate_snippet_notes_mentions_to_db_spec.rb
deleted file mode 100644
index 2ebe80e6ae3..00000000000
--- a/spec/migrations/migrate_snippet_notes_mentions_to_db_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20200127151953_migrate_snippet_notes_mentions_to_db')
-
-describe MigrateSnippetNotesMentionsToDb, :migration, :sidekiq do
- let(:users) { table(:users) }
- let(:projects) { table(:projects) }
- let(:namespaces) { table(:namespaces) }
- let(:snippets) { table(:snippets) }
- let(:snippet_user_mentions) { table(:snippet_user_mentions) }
- let(:notes) { table(:notes) }
-
- let(:user) { users.create!(name: 'root', email: 'root@example.com', username: 'root', projects_limit: 0) }
- let(:group) { namespaces.create!(name: 'group1', path: 'group1', owner_id: user.id) }
- let(:project) { projects.create!(name: 'gitlab1', path: 'gitlab1', namespace_id: group.id, visibility_level: 0) }
- let(:snippet) { snippets.create!(title: "title1", title_html: 'title1', description: 'snippet description with @root mention', project_id: project.id, author_id: user.id) }
-
- let!(:resource1) { notes.create!(note: 'note for @root to check', noteable_id: snippet.id, noteable_type: 'Snippet') }
- let!(:resource2) { notes.create!(note: 'note for @root to check', noteable_id: snippet.id, noteable_type: 'Snippet', system: true) }
- let!(:resource3) { notes.create!(note: 'note for @root to check', noteable_id: snippet.id, noteable_type: 'Snippet') }
-
- # non-migrateable resources
- # this note is already migrated, as it has a record in the snippet_user_mentions table
- let!(:resource4) { notes.create!(note: 'note for @root to check', noteable_id: snippet.id, noteable_type: 'Snippet') }
- let!(:user_mention) { snippet_user_mentions.create!(snippet_id: snippet.id, note_id: resource4.id, mentioned_users_ids: [1]) }
- # this note points to an innexistent noteable record
- let!(:resource5) { notes.create!(note: 'note for @root to check', noteable_id: snippets.maximum(:id) + 10, noteable_type: 'Snippet') }
-
- it_behaves_like 'schedules resource mentions migration', Snippet, true
-end
diff --git a/spec/models/snippet_repository_spec.rb b/spec/models/snippet_repository_spec.rb
index 5b16b945efe..120175fdd05 100644
--- a/spec/models/snippet_repository_spec.rb
+++ b/spec/models/snippet_repository_spec.rb
@@ -26,6 +26,44 @@ describe SnippetRepository do
end
end
+ describe '#create_file' do
+ let(:snippet) { create(:personal_snippet, :empty_repo, author: user) }
+
+ it 'creates the file' do
+ snippet_repository.create_file(user, 'foo', 'bar', commit_opts)
+ blob = first_blob(snippet)
+
+ aggregate_failures do
+ expect(blob).not_to be_nil
+ expect(blob.path).to eq 'foo'
+ expect(blob.data).to eq 'bar'
+ end
+ end
+
+ it 'fills the file path if empty' do
+ snippet_repository.create_file(user, nil, 'bar', commit_opts)
+ blob = first_blob(snippet)
+
+ aggregate_failures do
+ expect(blob).not_to be_nil
+ expect(blob.path).to eq 'snippetfile1.txt'
+ expect(blob.data).to eq 'bar'
+ end
+ end
+
+ context 'when the file exists' do
+ let(:snippet) { create(:personal_snippet, :repository, author: user) }
+
+ it 'captures the git exception and raises a SnippetRepository::CommitError' do
+ existing_blob = first_blob(snippet)
+
+ expect do
+ snippet_repository.create_file(user, existing_blob.path, existing_blob.data, commit_opts)
+ end.to raise_error described_class::CommitError
+ end
+ end
+ end
+
describe '#multi_files_action' do
let(:new_file) { { file_path: 'new_file_test', content: 'bar' } }
let(:move_file) { { previous_path: 'CHANGELOG', file_path: 'CHANGELOG_new', content: 'bar' } }
diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb
index 4d51deaa404..e00507d1827 100644
--- a/spec/services/projects/import_export/export_service_spec.rb
+++ b/spec/services/projects/import_export/export_service_spec.rb
@@ -64,6 +64,14 @@ describe Projects::ImportExport::ExportService do
service.execute
end
+ it 'saves the snippets' do
+ expect_next_instance_of(Gitlab::ImportExport::SnippetsRepoSaver) do |instance|
+ expect(instance).to receive(:save).and_call_original
+ end
+
+ service.execute
+ end
+
context 'when all saver services succeed' do
before do
allow(service).to receive(:save_services).and_return(true)
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
index f5b889ef720..8f5bfdacc3a 100644
--- a/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/background_migration/mentions_migration_shared_examples.rb
@@ -72,7 +72,7 @@ shared_examples 'schedules resource mentions migration' do |resource_class, is_f
it 'schedules background migrations' do
Sidekiq::Testing.fake! do
Timecop.freeze do
- resource_count = is_for_notes ? Note.where(noteable_type: resource_class.to_s).count : resource_class.count
+ resource_count = is_for_notes ? Note.count : resource_class.count
expect(resource_count).to eq 5
migrate!