Fix bug committing content when snippet created

When the snippet is created we commit the content
to the recently created repository. Nevertheless,
these actions are inside a db transaction and
the commit action triggers a request
handled by a different thread.

This means that in the thread the snippet
does not exists and, therefore, the operation
fails.
parent 02bd71ca
...@@ -52,8 +52,15 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -52,8 +52,15 @@ class Projects::SnippetsController < Projects::ApplicationController
create_params = snippet_params.merge(spammable_params) create_params = snippet_params.merge(spammable_params)
service_response = Snippets::CreateService.new(project, current_user, create_params).execute service_response = Snippets::CreateService.new(project, current_user, create_params).execute
@snippet = service_response.payload[:snippet] @snippet = service_response.payload[:snippet]
repository_operation_error = service_response.error? && !@snippet.persisted? && @snippet.valid?
recaptcha_check_with_fallback { render :new } if repository_operation_error
flash.now[:alert] = service_response.message
render :new
else
recaptcha_check_with_fallback { render :new }
end
end end
def update def update
......
...@@ -52,10 +52,17 @@ class SnippetsController < ApplicationController ...@@ -52,10 +52,17 @@ class SnippetsController < ApplicationController
create_params = snippet_params.merge(spammable_params) create_params = snippet_params.merge(spammable_params)
service_response = Snippets::CreateService.new(nil, current_user, create_params).execute service_response = Snippets::CreateService.new(nil, current_user, create_params).execute
@snippet = service_response.payload[:snippet] @snippet = service_response.payload[:snippet]
repository_operation_error = service_response.error? && !@snippet.persisted? && @snippet.valid?
move_temporary_files if @snippet.valid? && params[:files] if repository_operation_error
flash.now[:alert] = service_response.message
recaptcha_check_with_fallback { render :new } render :new
else
move_temporary_files if @snippet.valid? && params[:files]
recaptcha_check_with_fallback { render :new }
end
end end
def update def update
......
...@@ -38,25 +38,30 @@ module Snippets ...@@ -38,25 +38,30 @@ module Snippets
private private
def save_and_commit(snippet) def save_and_commit(snippet)
snippet.with_transaction_returning_status do result = snippet.with_transaction_returning_status do
(snippet.save && snippet.store_mentions!).tap do |saved| (snippet.save && snippet.store_mentions!).tap do |saved|
break false unless saved break false unless saved
if Feature.enabled?(:version_snippets, current_user) if Feature.enabled?(:version_snippets, current_user)
create_repository_for(snippet) create_repository_for(snippet)
create_commit(snippet)
end end
end end
rescue => e # Rescuing all because we can receive Creation exceptions, GRPC exceptions, Git exceptions, ... end
snippet.errors.add(:base, e.message)
# If the commit action failed we need to remove the repository if exists create_commit(snippet) if result && snippet.repository_exists?
if snippet.repository_exists?
Repositories::DestroyService.new(snippet.repository).execute
end
false result
end rescue => e # Rescuing all because we can receive Creation exceptions, GRPC exceptions, Git exceptions, ...
snippet.errors.add(:base, e.message)
# If the commit action failed we need to remove the repository if exists
snippet.repository.remove if snippet.repository_exists?
# If the snippet was created, we need to remove it as we
# would do like if it had had any validation error
snippet.delete if snippet.persisted?
false
end end
def create_repository_for(snippet) def create_repository_for(snippet)
......
---
title: Fix bug committing snippet content when creating the snippet
merge_request: 26287
author:
type: fixed
...@@ -95,6 +95,29 @@ shared_examples_for 'snippet editor' do ...@@ -95,6 +95,29 @@ shared_examples_for 'snippet editor' do
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z}) expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z})
end end
context 'when the git operation fails' do
let(:error) { 'This is a git error' }
before do
allow_next_instance_of(Snippets::CreateService) do |instance|
allow(instance).to receive(:create_commit).and_raise(StandardError, error)
end
fill_form
click_button('Create snippet')
wait_for_requests
end
it 'displays the error' do
expect(page).to have_content(error)
end
it 'renders new page' do
expect(page).to have_content('New Snippet')
end
end
end end
context 'when a user is not authenticated' do context 'when a user is not authenticated' do
......
...@@ -78,6 +78,29 @@ shared_examples_for 'snippet editor' do ...@@ -78,6 +78,29 @@ shared_examples_for 'snippet editor' do
expect(reqs.first.status_code).to eq(200) expect(reqs.first.status_code).to eq(200)
end end
context 'when the git operation fails' do
let(:error) { 'This is a git error' }
before do
allow_next_instance_of(Snippets::CreateService) do |instance|
allow(instance).to receive(:create_commit).and_raise(StandardError, error)
end
fill_form
click_button('Create snippet')
wait_for_requests
end
it 'displays the error' do
expect(page).to have_content(error)
end
it 'renders new page' do
expect(page).to have_content('New Snippet')
end
end
it 'validation fails for the first time' do it 'validation fails for the first time' do
fill_in 'personal_snippet_title', with: 'My Snippet Title' fill_in 'personal_snippet_title', with: 'My Snippet Title'
click_button('Create snippet') click_button('Create snippet')
......
...@@ -185,12 +185,10 @@ describe Snippets::CreateService do ...@@ -185,12 +185,10 @@ describe Snippets::CreateService do
expect { subject }.not_to change { Snippet.count } expect { subject }.not_to change { Snippet.count }
end end
it 'does not create the repository' do it 'destroys the created repository' do
expect(snippet.repository_exists?).to be_falsey expect_next_instance_of(Repository) do |instance|
end expect(instance).to receive(:remove).and_call_original
end
it 'destroys the existing repository' do
expect(Repositories::DestroyService).to receive(:new).and_call_original
subject subject
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment